From b9a2918b27bf1c6a4324b01b5cab77de94eb9117 Mon Sep 17 00:00:00 2001 From: Carpe-Wang Date: Sat, 19 Apr 2025 16:58:51 -0400 Subject: [PATCH] APi test --- build.gradle | 42 +- .../ConvertMarkdownToPdfIntegrationTest.java | 3 - .../software/SPDF/utils/FileToPdfTest.java | 82 - testResult/html/index.html | 1 + testResult/html/jacoco-resources/branchfc.gif | Bin 0 -> 91 bytes testResult/html/jacoco-resources/branchnc.gif | Bin 0 -> 91 bytes testResult/html/jacoco-resources/branchpc.gif | Bin 0 -> 91 bytes testResult/html/jacoco-resources/bundle.gif | Bin 0 -> 709 bytes testResult/html/jacoco-resources/class.gif | Bin 0 -> 586 bytes testResult/html/jacoco-resources/down.gif | Bin 0 -> 67 bytes testResult/html/jacoco-resources/greenbar.gif | Bin 0 -> 91 bytes testResult/html/jacoco-resources/group.gif | Bin 0 -> 351 bytes testResult/html/jacoco-resources/method.gif | Bin 0 -> 193 bytes testResult/html/jacoco-resources/package.gif | Bin 0 -> 227 bytes testResult/html/jacoco-resources/prettify.css | 13 + testResult/html/jacoco-resources/prettify.js | 1510 +++++++++++++++++ testResult/html/jacoco-resources/redbar.gif | Bin 0 -> 91 bytes testResult/html/jacoco-resources/report.css | 243 +++ testResult/html/jacoco-resources/report.gif | Bin 0 -> 363 bytes testResult/html/jacoco-resources/session.gif | Bin 0 -> 213 bytes testResult/html/jacoco-resources/sort.gif | Bin 0 -> 58 bytes testResult/html/jacoco-resources/sort.js | 148 ++ testResult/html/jacoco-resources/source.gif | Bin 0 -> 354 bytes testResult/html/jacoco-resources/up.gif | Bin 0 -> 67 bytes testResult/html/jacoco-sessions.html | 1 + .../CMSProcessableInputStream.html | 1 + .../CMSProcessableInputStream.java.html | 65 + .../CreateSignatureBase.html | 1 + .../CreateSignatureBase.java.html | 171 ++ .../TSAClient.html | 1 + .../TSAClient.java.html | 175 ++ .../ValidationTimeStamp.html | 1 + .../ValidationTimeStamp.java.html | 135 ++ .../index.html | 1 + .../index.source.html | 1 + .../ConnectedInputStream.html | 1 + .../ConnectedInputStream.java.html | 83 + .../DeletingRandomAccessFile.html | 1 + .../DeletingRandomAccessFile.java.html | 40 + .../index.html | 1 + .../index.source.html | 1 + .../EEAppConfig.html | 1 + .../EEAppConfig.java.html | 126 ++ .../KeygenLicenseVerifier$License.html | 1 + .../KeygenLicenseVerifier.html | 1 + .../KeygenLicenseVerifier.java.html | 582 +++++++ .../LicenseKeyChecker.html | 1 + .../LicenseKeyChecker.java.html | 101 ++ .../html/stirling.software.SPDF.EE/index.html | 1 + .../index.source.html | 1 + .../ReplaceAndInvertColorFactory.html | 1 + .../ReplaceAndInvertColorFactory.java.html | 40 + .../index.html | 1 + .../index.source.html | 1 + .../DesktopBrowser$1.html | 1 + .../DesktopBrowser$2.html | 1 + .../DesktopBrowser$3.html | 1 + .../DesktopBrowser$4.html | 1 + .../DesktopBrowser$5.html | 1 + .../DesktopBrowser$6.html | 1 + .../DesktopBrowser.html | 1 + .../DesktopBrowser.java.html | 498 ++++++ .../LoadingWindow.html | 1 + .../LoadingWindow.java.html | 352 ++++ .../stirling.software.SPDF.UI.impl/index.html | 1 + .../index.source.html | 1 + .../ShowAdminInterface.html | 1 + .../ShowAdminInterface.java.html | 8 + .../index.html | 1 + .../index.source.html | 1 + .../DatabaseConfig.html | 1 + .../DatabaseConfig.java.html | 147 ++ .../DatabaseService.html | 1 + .../DatabaseService.java.html | 318 ++++ .../ScheduledTasks.html | 1 + .../ScheduledTasks.java.html | 28 + .../index.html | 1 + .../index.source.html | 1 + ...tomOAuth2AuthenticationFailureHandler.html | 1 + ...uth2AuthenticationFailureHandler.java.html | 64 + ...tomOAuth2AuthenticationSuccessHandler.html | 1 + ...uth2AuthenticationSuccessHandler.java.html | 108 ++ .../CustomOAuth2UserService.html | 1 + .../CustomOAuth2UserService.java.html | 84 + .../OAuth2Configuration.html | 1 + .../OAuth2Configuration.java.html | 253 +++ .../index.html | 1 + .../index.source.html | 1 + .../CertificateUtils.html | 1 + .../CertificateUtils.java.html | 57 + .../CustomSaml2AuthenticatedPrincipal.html | 1 + ...ustomSaml2AuthenticatedPrincipal.java.html | 28 + ...stomSaml2AuthenticationFailureHandler.html | 1 + ...aml2AuthenticationFailureHandler.java.html | 42 + ...stomSaml2AuthenticationSuccessHandler.html | 1 + ...aml2AuthenticationSuccessHandler.java.html | 128 ++ ...mSaml2ResponseAuthenticationConverter.html | 1 + ...2ResponseAuthenticationConverter.java.html | 117 ++ .../SAML2Configuration.html | 1 + .../SAML2Configuration.java.html | 177 ++ .../index.html | 1 + .../index.source.html | 1 + .../CustomHttpSessionListener.html | 1 + .../CustomHttpSessionListener.java.html | 31 + .../SessionPersistentRegistry$1.html | 1 + .../SessionPersistentRegistry.html | 1 + .../SessionPersistentRegistry.java.html | 193 +++ .../SessionRegistryConfig.html | 1 + .../SessionRegistryConfig.java.html | 21 + .../SessionScheduled.html | 1 + .../SessionScheduled.java.html | 39 + .../index.html | 1 + .../index.source.html | 1 + .../AppUpdateAuthService.html | 1 + .../AppUpdateAuthService.java.html | 48 + .../CustomAuthenticationFailureHandler.html | 1 + ...stomAuthenticationFailureHandler.java.html | 91 + .../CustomAuthenticationSuccessHandler.html | 1 + ...stomAuthenticationSuccessHandler.java.html | 63 + .../CustomLogoutSuccessHandler.html | 1 + .../CustomLogoutSuccessHandler.java.html | 240 +++ .../CustomUserDetailsService.html | 1 + .../CustomUserDetailsService.java.html | 63 + .../FirstLoginFilter.html | 1 + .../FirstLoginFilter.java.html | 77 + .../IPRateLimitingFilter.html | 1 + .../IPRateLimitingFilter.java.html | 67 + .../InitialSecuritySetup.html | 1 + .../InitialSecuritySetup.java.html | 97 ++ .../LoginAttemptService.html | 1 + .../LoginAttemptService.java.html | 92 + .../RateLimitResetScheduler.html | 1 + .../RateLimitResetScheduler.java.html | 20 + .../SecurityConfiguration.html | 1 + .../SecurityConfiguration.java.html | 320 ++++ .../UserAuthenticationFilter$LoginMethod.html | 1 + .../UserAuthenticationFilter.html | 1 + .../UserAuthenticationFilter.java.html | 260 +++ .../UserBasedRateLimitingFilter.html | 1 + .../UserBasedRateLimitingFilter.java.html | 145 ++ .../UserService.html | 1 + .../UserService.java.html | 481 ++++++ .../index.html | 1 + .../index.source.html | 1 + .../AppConfig.html | 1 + .../AppConfig.java.html | 199 +++ .../AppUpdateService.html | 1 + .../AppUpdateService.java.html | 33 + .../CleanUrlInterceptor.html | 1 + .../CleanUrlInterceptor.java.html | 84 + .../ConfigInitializer.html | 1 + .../ConfigInitializer.java.html | 125 ++ .../EndpointConfiguration.html | 1 + .../EndpointConfiguration.java.html | 301 ++++ .../EndpointInspector.html | 1 + .../EndpointInspector.java.html | 216 +++ .../EndpointInterceptor.html | 1 + .../EndpointInterceptor.java.html | 33 + .../EnterpriseEndpointFilter.html | 1 + .../EnterpriseEndpointFilter.java.html | 39 + .../ExternalAppDepConfig$1.html | 1 + .../ExternalAppDepConfig.html | 1 + .../ExternalAppDepConfig.java.html | 157 ++ .../FileFallbackTemplateResolver.html | 1 + .../FileFallbackTemplateResolver.java.html | 56 + .../InitialSetup.html | 1 + .../InitialSetup.java.html | 104 ++ .../InstallationPathConfig.html | 1 + .../InstallationPathConfig.java.html | 115 ++ .../LocaleConfiguration.html | 1 + .../LocaleConfiguration.java.html | 63 + .../LogbackPropertyLoader.html | 1 + .../LogbackPropertyLoader.java.html | 11 + .../MetricsConfig$1.html | 1 + .../MetricsConfig.html | 1 + .../MetricsConfig.java.html | 26 + .../MetricsFilter.html | 1 + .../MetricsFilter.java.html | 52 + .../OpenApiConfig.html | 1 + .../OpenApiConfig.java.html | 57 + .../PostHogConfig.html | 1 + .../PostHogConfig.java.html | 42 + .../PostHogLoggerImpl.html | 1 + .../PostHogLoggerImpl.java.html | 43 + .../RuntimePathConfig.html | 1 + .../RuntimePathConfig.java.html | 80 + .../StartupApplicationListener.html | 1 + .../StartupApplicationListener.java.html | 19 + .../WebMvcConfig.html | 1 + .../WebMvcConfig.java.html | 31 + .../YamlHelper$1.html | 1 + .../YamlHelper.html | 1 + .../YamlHelper.java.html | 480 ++++++ .../YamlPropertySourceFactory.html | 1 + .../YamlPropertySourceFactory.java.html | 25 + .../stirling.software.SPDF.config/index.html | 1 + .../index.source.html | 1 + .../ConvertHtmlToPDF.html | 1 + .../ConvertHtmlToPDF.java.html | 85 + .../ConvertImgPDFController.html | 1 + .../ConvertImgPDFController.java.html | 284 ++++ .../ConvertMarkdownToPdf.html | 1 + .../ConvertMarkdownToPdf.java.html | 111 ++ .../ConvertOfficeController.html | 1 + .../ConvertOfficeController.java.html | 118 ++ .../ConvertPDFToHtml.html | 1 + .../ConvertPDFToHtml.java.html | 33 + .../ConvertPDFToOffice.html | 1 + .../ConvertPDFToOffice.java.html | 111 ++ .../ConvertPDFToPDFA.html | 1 + .../ConvertPDFToPDFA.java.html | 127 ++ .../ConvertWebsiteToPDF.html | 1 + .../ConvertWebsiteToPDF.java.html | 118 ++ .../ExtractCSVController$CsvEntry.html | 1 + .../ExtractCSVController.html | 1 + .../ExtractCSVController.java.html | 139 ++ .../TableAttributeProvider.html | 1 + .../index.html | 1 + .../index.source.html | 1 + .../FilterController.html | 1 + .../FilterController.java.html | 221 +++ .../index.html | 1 + .../index.source.html | 1 + .../AutoRenameController$1$LineInfo.html | 1 + .../AutoRenameController$1.html | 1 + .../AutoRenameController.html | 1 + .../AutoRenameController.java.html | 150 ++ .../AutoSplitPdfController.html | 1 + .../AutoSplitPdfController.java.html | 215 +++ .../BlankPageController.html | 1 + .../BlankPageController.java.html | 175 ++ .../CompressController$CompressionStats.html | 1 + .../CompressController.html | 1 + .../CompressController.java.html | 868 ++++++++++ .../DecompressPdfController.html | 1 + .../DecompressPdfController.java.html | 146 ++ .../ExtractImageScansController.html | 1 + .../ExtractImageScansController.java.html | 239 +++ .../ExtractImagesController.html | 1 + .../ExtractImagesController.java.html | 240 +++ .../FakeScanControllerWIP.html | 1 + .../FakeScanControllerWIP.java.html | 312 ++++ .../FlattenController.html | 1 + .../FlattenController.java.html | 94 + .../MetadataController.html | 1 + .../MetadataController.java.html | 190 +++ .../OCRController.html | 1 + .../OCRController.java.html | 218 +++ .../OverlayImageController.html | 1 + .../OverlayImageController.java.html | 69 + .../PageNumbersController.html | 1 + .../PageNumbersController.java.html | 178 ++ .../PrintFileController$1.html | 1 + .../PrintFileController.html | 1 + .../PrintFileController.java.html | 108 ++ .../RepairController.html | 1 + .../RepairController.java.html | 81 + .../ReplaceAndInvertColorController.html | 1 + .../ReplaceAndInvertColorController.java.html | 58 + .../ShowJavascript.html | 1 + .../ShowJavascript.java.html | 88 + .../StampController.html | 1 + .../StampController.java.html | 345 ++++ .../index.html | 1 + .../index.source.html | 1 + .../ApiDocService.html | 1 + .../ApiDocService.java.html | 169 ++ .../PipelineController.html | 1 + .../PipelineController.java.html | 134 ++ .../PipelineDirectoryProcessor$1.html | 1 + .../PipelineDirectoryProcessor.html | 1 + .../PipelineDirectoryProcessor.java.html | 407 +++++ .../PipelineProcessor$1.html | 1 + .../PipelineProcessor$2.html | 1 + .../PipelineProcessor$3.html | 1 + .../PipelineProcessor$4.html | 1 + .../PipelineProcessor.html | 1 + .../PipelineProcessor.java.html | 376 ++++ .../index.html | 1 + .../index.source.html | 1 + .../CertSignController$CreateSignature.html | 1 + .../CertSignController.html | 1 + .../CertSignController.java.html | 334 ++++ .../GetInfoOnPDF.html | 1 + .../GetInfoOnPDF.java.html | 751 ++++++++ .../PasswordController.html | 1 + .../PasswordController.java.html | 109 ++ .../RedactController.html | 1 + .../RedactController.java.html | 274 +++ .../RemoveCertSignController.html | 1 + .../RemoveCertSignController.java.html | 74 + .../SanitizeController.html | 1 + .../SanitizeController.java.html | 195 +++ .../ValidateSignatureController.html | 1 + .../ValidateSignatureController.java.html | 182 ++ .../WatermarkController.html | 1 + .../WatermarkController.java.html | 301 ++++ .../index.html | 1 + .../index.source.html | 1 + .../AdditionalLanguageJsController.html | 1 + .../AdditionalLanguageJsController.java.html | 71 + .../AnalysisController.html | 1 + .../AnalysisController.java.html | 203 +++ .../CropController.html | 1 + .../CropController.java.html | 97 ++ .../DatabaseController.html | 1 + .../DatabaseController.java.html | 168 ++ .../H2SQLCondition.html | 1 + .../H2SQLCondition.java.html | 20 + .../MergeController.html | 1 + .../MergeController.java.html | 205 +++ .../MultiPageLayoutController.html | 1 + .../MultiPageLayoutController.java.html | 150 ++ .../PdfImageRemovalController.html | 1 + .../PdfImageRemovalController.java.html | 89 + .../PdfOverlayController.html | 1 + .../PdfOverlayController.java.html | 213 +++ .../RearrangePagesPDFController.html | 1 + .../RearrangePagesPDFController.java.html | 300 ++++ .../RotationController.html | 1 + .../RotationController.java.html | 69 + .../ScalePagesController.html | 1 + .../ScalePagesController.java.html | 149 ++ .../SettingsController.html | 1 + .../SettingsController.java.html | 45 + .../SplitPDFController.html | 1 + .../SplitPDFController.java.html | 168 ++ .../SplitPdfByChaptersController.html | 1 + .../SplitPdfByChaptersController.java.html | 317 ++++ .../SplitPdfBySectionsController.html | 1 + .../SplitPdfBySectionsController.java.html | 166 ++ .../SplitPdfBySizeController.html | 1 + .../SplitPdfBySizeController.java.html | 476 ++++++ .../ToSinglePageController.html | 1 + .../ToSinglePageController.java.html | 101 ++ .../UserController.html | 1 + .../UserController.java.html | 385 +++++ .../index.html | 1 + .../index.source.html | 1 + .../AccountWebController.html | 1 + .../AccountWebController.java.html | 449 +++++ .../ConverterWebController.html | 1 + .../ConverterWebController.java.html | 125 ++ .../DatabaseWebController.html | 1 + .../DatabaseWebController.java.html | 48 + .../GeneralWebController$1.html | 1 + .../GeneralWebController$FontResource.html | 1 + .../GeneralWebController.html | 1 + .../GeneralWebController.java.html | 347 ++++ .../HomeWebController$1.html | 1 + .../HomeWebController.html | 1 + .../HomeWebController.java.html | 96 ++ .../MetricsController$EndpointCount.html | 1 + .../MetricsController.html | 1 + .../MetricsController.java.html | 400 +++++ .../OtherWebController.html | 1 + .../OtherWebController.java.html | 189 +++ .../SecurityWebController.html | 1 + .../SecurityWebController.java.html | 90 + .../SignatureController.html | 1 + .../SignatureController.java.html | 50 + .../index.html | 1 + .../index.source.html | 1 + .../ConvertPDFToMarkdown.html | 1 + .../ConvertPDFToMarkdown.java.html | 33 + .../index.html | 1 + .../index.source.html | 1 + .../HighContrastColorCombination.html | 1 + .../HighContrastColorCombination.java.html | 9 + .../OptimizePdfRequest.html | 1 + .../OptimizePdfRequest.java.html | 43 + .../ReplaceAndInvert.html | 1 + .../ReplaceAndInvert.java.html | 8 + .../index.html | 1 + .../index.source.html | 1 + .../PDFWithPageNums.html | 1 + .../PDFWithPageNums.java.html | 40 + .../index.html | 1 + .../index.source.html | 1 + .../BackupNotFoundException.html | 1 + .../BackupNotFoundException.java.html | 8 + .../NoProviderFoundException.html | 1 + .../NoProviderFoundException.java.html | 12 + .../UnsupportedProviderException.html | 1 + .../UnsupportedProviderException.java.html | 8 + .../UnsupportedUsernameAttribute.html | 1 + .../UnsupportedUsernameAttribute.java.html | 8 + .../index.html | 1 + .../index.source.html | 1 + .../GitHubProvider.html | 1 + .../GitHubProvider.java.html | 87 + .../GoogleProvider.html | 1 + .../GoogleProvider.java.html | 86 + .../KeycloakProvider.html | 1 + .../KeycloakProvider.java.html | 73 + .../Provider.html | 1 + .../Provider.java.html | 130 ++ .../index.html | 1 + .../index.source.html | 1 + .../ApiEndpoint.html | 1 + .../ApiEndpoint.java.html | 43 + .../ApiKeyAuthenticationToken.html | 1 + .../ApiKeyAuthenticationToken.java.html | 53 + .../ApplicationProperties$Driver.html | 1 + ...ties$EnterpriseEdition$CustomMetadata.html | 1 + ...es$Premium$ProFeatures$CustomMetadata.html | 1 + ...rties$Premium$ProFeatures$GoogleDrive.html | 1 + ...operties$ProcessExecutor$SessionLimit.html | 1 + ...erties$ProcessExecutor$TimeoutMinutes.html | 1 + ...ationProperties$Security$LoginMethods.html | 1 + ...tionProperties$Security$OAUTH2$Client.html | 1 + ...ApplicationProperties$Security$OAUTH2.html | 1 + .../ApplicationProperties$Security$SAML2.html | 1 + .../ApplicationProperties$Security.html | 1 + .../ApplicationProperties$System.html | 1 + .../ApplicationProperties$Ui.html | 1 + .../ApplicationProperties.html | 1 + .../ApplicationProperties.java.html | 581 +++++++ .../AttemptCounter.html | 1 + .../AttemptCounter.java.html | 34 + .../AuthenticationType.html | 1 + .../AuthenticationType.java.html | 7 + .../Authority.html | 1 + .../Authority.java.html | 56 + .../InputStreamTemplateResource.html | 1 + .../InputStreamTemplateResource.java.html | 46 + .../PersistentLogin.html | 1 + .../PersistentLogin.java.html | 61 + .../PipelineConfig.html | 1 + .../PipelineConfig.java.html | 50 + .../PipelineOperation.html | 1 + .../PipelineOperation.java.html | 30 + .../stirling.software.SPDF.model/Role.html | 1 + .../Role.java.html | 85 + .../SortTypes.html | 1 + .../SortTypes.java.html | 16 + .../stirling.software.SPDF.model/User.html | 1 + .../User.java.html | 148 ++ .../UsernameAttribute.html | 1 + .../UsernameAttribute.java.html | 27 + .../stirling.software.SPDF.model/index.html | 1 + .../index.source.html | 1 + .../FlexibleCSVWriter.html | 1 + .../FlexibleCSVWriter.java.html | 17 + .../TextFinder$MatchInfo.html | 1 + .../TextFinder.html | 1 + .../TextFinder.java.html | 108 ++ .../stirling.software.SPDF.pdf/index.html | 1 + .../index.source.html | 1 + .../JPATokenRepositoryImpl.html | 1 + .../JPATokenRepositoryImpl.java.html | 60 + .../index.html | 1 + .../index.source.html | 1 + .../ReplaceAndInvertColorService.html | 1 + .../ReplaceAndInvertColorService.java.html | 43 + .../index.html | 1 + .../index.source.html | 1 + .../CertificateValidationService.html | 1 + .../CertificateValidationService.java.html | 144 ++ .../CustomPDFDocumentFactory.html | 1 + .../CustomPDFDocumentFactory.java.html | 477 ++++++ .../LanguageService.html | 1 + .../LanguageService.java.html | 58 + .../MetricsAggregatorService.html | 1 + .../MetricsAggregatorService.java.html | 99 ++ .../PdfImageRemovalService.html | 1 + .../PdfImageRemovalService.java.html | 52 + .../PdfMetadataService.html | 1 + .../PdfMetadataService.java.html | 116 ++ .../PostHogService.html | 1 + .../PostHogService.java.html | 416 +++++ .../SignatureService.html | 1 + .../SignatureService.java.html | 106 ++ .../stirling.software.SPDF.service/index.html | 1 + .../index.source.html | 1 + .../CustomColorReplaceStrategy.html | 1 + .../CustomColorReplaceStrategy.java.html | 171 ++ .../HighContrastColorReplaceDecider.html | 1 + .../HighContrastColorReplaceDecider.java.html | 31 + .../InvertFullColorStrategy.html | 1 + .../InvertFullColorStrategy.java.html | 123 ++ .../PdfTextStripperCustom.html | 1 + .../PdfTextStripperCustom.java.html | 37 + .../ReplaceAndInvertColorStrategy.html | 1 + .../ReplaceAndInvertColorStrategy.java.html | 27 + .../index.html | 1 + .../index.source.html | 1 + .../StringToArrayListPropertyEditor$1.html | 1 + .../StringToArrayListPropertyEditor.html | 1 + .../StringToArrayListPropertyEditor.java.html | 39 + .../StringToMapPropertyEditor$1.html | 1 + .../StringToMapPropertyEditor.html | 1 + .../StringToMapPropertyEditor.java.html | 27 + .../index.html | 1 + .../index.source.html | 1 + .../Validator.html | 1 + .../Validator.java.html | 37 + .../index.html | 1 + .../index.source.html | 1 + .../CheckProgramInstall.html | 1 + .../CheckProgramInstall.java.html | 60 + .../CustomHtmlSanitizer.html | 1 + .../CustomHtmlSanitizer.java.html | 22 + .../ErrorUtils.html | 1 + .../ErrorUtils.java.html | 32 + .../FileInfo.html | 1 + .../FileInfo.java.html | 51 + .../FileMonitor.html | 1 + .../FileMonitor.java.html | 206 +++ .../FileToPdf$1.html | 1 + .../FileToPdf.html | 1 + .../FileToPdf.java.html | 208 +++ .../GeneralUtils$1.html | 1 + .../GeneralUtils.html | 1 + .../GeneralUtils.java.html | 454 +++++ .../ImageProcessingUtils.html | 1 + .../ImageProcessingUtils.java.html | 123 ++ .../PDFToFile.html | 1 + .../PDFToFile.java.html | 319 ++++ .../PdfUtils$PdfImageDimensionValue.html | 1 + .../PdfUtils$PdfRenderSettingsKey.html | 1 + .../PdfUtils.html | 1 + .../PdfUtils.java.html | 528 ++++++ ...ProcessExecutor$ProcessExecutorResult.html | 1 + .../ProcessExecutor$Processes.html | 1 + .../ProcessExecutor.html | 1 + .../ProcessExecutor.java.html | 310 ++++ .../PropertyConfigs.html | 1 + .../PropertyConfigs.java.html | 39 + .../RequestUriUtils.html | 1 + .../RequestUriUtils.java.html | 51 + .../UIScaling.html | 1 + .../UIScaling.java.html | 68 + .../UrlUtils.html | 1 + .../UrlUtils.java.html | 35 + .../WebResponseUtils.html | 1 + .../WebResponseUtils.java.html | 70 + .../stirling.software.SPDF.utils/index.html | 1 + .../index.source.html | 1 + .../LibreOfficeListener.html | 1 + .../LibreOfficeListener.java.html | 99 ++ .../SPDFApplication.html | 1 + .../SPDFApplication.java.html | 233 +++ .../html/stirling.software.SPDF/index.html | 1 + .../stirling.software.SPDF/index.source.html | 1 + 545 files changed, 31422 insertions(+), 97 deletions(-) delete mode 100644 src/test/java/stirling/software/SPDF/utils/FileToPdfTest.java create mode 100644 testResult/html/index.html create mode 100644 testResult/html/jacoco-resources/branchfc.gif create mode 100644 testResult/html/jacoco-resources/branchnc.gif create mode 100644 testResult/html/jacoco-resources/branchpc.gif create mode 100644 testResult/html/jacoco-resources/bundle.gif create mode 100644 testResult/html/jacoco-resources/class.gif create mode 100644 testResult/html/jacoco-resources/down.gif create mode 100644 testResult/html/jacoco-resources/greenbar.gif create mode 100644 testResult/html/jacoco-resources/group.gif create mode 100644 testResult/html/jacoco-resources/method.gif create mode 100644 testResult/html/jacoco-resources/package.gif create mode 100644 testResult/html/jacoco-resources/prettify.css create mode 100644 testResult/html/jacoco-resources/prettify.js create mode 100644 testResult/html/jacoco-resources/redbar.gif create mode 100644 testResult/html/jacoco-resources/report.css create mode 100644 testResult/html/jacoco-resources/report.gif create mode 100644 testResult/html/jacoco-resources/session.gif create mode 100644 testResult/html/jacoco-resources/sort.gif create mode 100644 testResult/html/jacoco-resources/sort.js create mode 100644 testResult/html/jacoco-resources/source.gif create mode 100644 testResult/html/jacoco-resources/up.gif create mode 100644 testResult/html/jacoco-sessions.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/CMSProcessableInputStream.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/CMSProcessableInputStream.java.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/CreateSignatureBase.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/CreateSignatureBase.java.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/TSAClient.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/TSAClient.java.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/ValidationTimeStamp.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/ValidationTimeStamp.java.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/index.html create mode 100644 testResult/html/org.apache.pdfbox.examples.signature/index.source.html create mode 100644 testResult/html/org.apache.pdfbox.examples.util/ConnectedInputStream.html create mode 100644 testResult/html/org.apache.pdfbox.examples.util/ConnectedInputStream.java.html create mode 100644 testResult/html/org.apache.pdfbox.examples.util/DeletingRandomAccessFile.html create mode 100644 testResult/html/org.apache.pdfbox.examples.util/DeletingRandomAccessFile.java.html create mode 100644 testResult/html/org.apache.pdfbox.examples.util/index.html create mode 100644 testResult/html/org.apache.pdfbox.examples.util/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.EE/EEAppConfig.html create mode 100644 testResult/html/stirling.software.SPDF.EE/EEAppConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier$License.html create mode 100644 testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier.html create mode 100644 testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier.java.html create mode 100644 testResult/html/stirling.software.SPDF.EE/LicenseKeyChecker.html create mode 100644 testResult/html/stirling.software.SPDF.EE/LicenseKeyChecker.java.html create mode 100644 testResult/html/stirling.software.SPDF.EE/index.html create mode 100644 testResult/html/stirling.software.SPDF.EE/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.Factories/ReplaceAndInvertColorFactory.html create mode 100644 testResult/html/stirling.software.SPDF.Factories/ReplaceAndInvertColorFactory.java.html create mode 100644 testResult/html/stirling.software.SPDF.Factories/index.html create mode 100644 testResult/html/stirling.software.SPDF.Factories/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$1.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$2.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$3.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$4.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$5.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$6.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser.java.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/LoadingWindow.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/LoadingWindow.java.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/index.html create mode 100644 testResult/html/stirling.software.SPDF.UI.impl/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.config.interfaces/ShowAdminInterface.html create mode 100644 testResult/html/stirling.software.SPDF.config.interfaces/ShowAdminInterface.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.interfaces/index.html create mode 100644 testResult/html/stirling.software.SPDF.config.interfaces/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.database/DatabaseConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.database/DatabaseConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.database/DatabaseService.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.database/DatabaseService.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.database/ScheduledTasks.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.database/ScheduledTasks.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.database/index.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.database/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationFailureHandler.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationFailureHandler.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationSuccessHandler.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationSuccessHandler.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2UserService.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2UserService.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/OAuth2Configuration.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/OAuth2Configuration.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/index.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.oauth2/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CertificateUtils.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CertificateUtils.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticatedPrincipal.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticatedPrincipal.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationFailureHandler.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationFailureHandler.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationSuccessHandler.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationSuccessHandler.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2ResponseAuthenticationConverter.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2ResponseAuthenticationConverter.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/SAML2Configuration.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/SAML2Configuration.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/index.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.saml2/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/CustomHttpSessionListener.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/CustomHttpSessionListener.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry$1.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/SessionRegistryConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/SessionRegistryConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/SessionScheduled.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/SessionScheduled.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/index.html create mode 100644 testResult/html/stirling.software.SPDF.config.security.session/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/AppUpdateAuthService.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/AppUpdateAuthService.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationFailureHandler.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationFailureHandler.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationSuccessHandler.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationSuccessHandler.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/CustomLogoutSuccessHandler.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/CustomLogoutSuccessHandler.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/CustomUserDetailsService.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/CustomUserDetailsService.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/FirstLoginFilter.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/FirstLoginFilter.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/IPRateLimitingFilter.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/IPRateLimitingFilter.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/InitialSecuritySetup.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/InitialSecuritySetup.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/LoginAttemptService.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/LoginAttemptService.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/RateLimitResetScheduler.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/RateLimitResetScheduler.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/SecurityConfiguration.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/SecurityConfiguration.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter$LoginMethod.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/UserBasedRateLimitingFilter.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/UserBasedRateLimitingFilter.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/UserService.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/UserService.java.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/index.html create mode 100644 testResult/html/stirling.software.SPDF.config.security/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.config/AppConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config/AppConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/AppUpdateService.html create mode 100644 testResult/html/stirling.software.SPDF.config/AppUpdateService.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/CleanUrlInterceptor.html create mode 100644 testResult/html/stirling.software.SPDF.config/CleanUrlInterceptor.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/ConfigInitializer.html create mode 100644 testResult/html/stirling.software.SPDF.config/ConfigInitializer.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/EndpointConfiguration.html create mode 100644 testResult/html/stirling.software.SPDF.config/EndpointConfiguration.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/EndpointInspector.html create mode 100644 testResult/html/stirling.software.SPDF.config/EndpointInspector.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/EndpointInterceptor.html create mode 100644 testResult/html/stirling.software.SPDF.config/EndpointInterceptor.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/EnterpriseEndpointFilter.html create mode 100644 testResult/html/stirling.software.SPDF.config/EnterpriseEndpointFilter.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig$1.html create mode 100644 testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/FileFallbackTemplateResolver.html create mode 100644 testResult/html/stirling.software.SPDF.config/FileFallbackTemplateResolver.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/InitialSetup.html create mode 100644 testResult/html/stirling.software.SPDF.config/InitialSetup.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/InstallationPathConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config/InstallationPathConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/LocaleConfiguration.html create mode 100644 testResult/html/stirling.software.SPDF.config/LocaleConfiguration.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/LogbackPropertyLoader.html create mode 100644 testResult/html/stirling.software.SPDF.config/LogbackPropertyLoader.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/MetricsConfig$1.html create mode 100644 testResult/html/stirling.software.SPDF.config/MetricsConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config/MetricsConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/MetricsFilter.html create mode 100644 testResult/html/stirling.software.SPDF.config/MetricsFilter.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/OpenApiConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config/OpenApiConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/PostHogConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config/PostHogConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/PostHogLoggerImpl.html create mode 100644 testResult/html/stirling.software.SPDF.config/PostHogLoggerImpl.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/RuntimePathConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config/RuntimePathConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/StartupApplicationListener.html create mode 100644 testResult/html/stirling.software.SPDF.config/StartupApplicationListener.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/WebMvcConfig.html create mode 100644 testResult/html/stirling.software.SPDF.config/WebMvcConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/YamlHelper$1.html create mode 100644 testResult/html/stirling.software.SPDF.config/YamlHelper.html create mode 100644 testResult/html/stirling.software.SPDF.config/YamlHelper.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/YamlPropertySourceFactory.html create mode 100644 testResult/html/stirling.software.SPDF.config/YamlPropertySourceFactory.java.html create mode 100644 testResult/html/stirling.software.SPDF.config/index.html create mode 100644 testResult/html/stirling.software.SPDF.config/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertHtmlToPDF.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertHtmlToPDF.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertImgPDFController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertImgPDFController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertOfficeController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertOfficeController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToHtml.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToHtml.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToOffice.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToOffice.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToPDFA.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToPDFA.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertWebsiteToPDF.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ConvertWebsiteToPDF.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController$CsvEntry.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/TableAttributeProvider.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/index.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.converters/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.filters/FilterController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.filters/FilterController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.filters/index.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.filters/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController$1$LineInfo.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController$1.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/AutoSplitPdfController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/AutoSplitPdfController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/BlankPageController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/BlankPageController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/CompressController$CompressionStats.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/CompressController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/CompressController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/DecompressPdfController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/DecompressPdfController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImageScansController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImageScansController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImagesController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImagesController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/FakeScanControllerWIP.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/FakeScanControllerWIP.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/FlattenController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/FlattenController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/MetadataController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/MetadataController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/OCRController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/OCRController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/OverlayImageController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/OverlayImageController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/PageNumbersController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/PageNumbersController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController$1.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/RepairController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/RepairController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/ReplaceAndInvertColorController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/ReplaceAndInvertColorController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/ShowJavascript.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/ShowJavascript.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/StampController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/StampController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/index.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.misc/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/ApiDocService.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/ApiDocService.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor$1.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$1.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$2.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$3.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$4.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/index.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.pipeline/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/CertSignController$CreateSignature.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/CertSignController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/CertSignController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/GetInfoOnPDF.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/GetInfoOnPDF.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/PasswordController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/PasswordController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/RedactController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/RedactController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/RemoveCertSignController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/RemoveCertSignController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/SanitizeController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/SanitizeController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/ValidateSignatureController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/ValidateSignatureController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/WatermarkController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/WatermarkController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/index.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api.security/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/AdditionalLanguageJsController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/AdditionalLanguageJsController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/AnalysisController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/AnalysisController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/CropController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/CropController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/DatabaseController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/DatabaseController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/H2SQLCondition.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/H2SQLCondition.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/MergeController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/MergeController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/MultiPageLayoutController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/MultiPageLayoutController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/PdfImageRemovalController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/PdfImageRemovalController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/PdfOverlayController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/PdfOverlayController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/RearrangePagesPDFController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/RearrangePagesPDFController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/RotationController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/RotationController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/ScalePagesController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/ScalePagesController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SettingsController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SettingsController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SplitPDFController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SplitPDFController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SplitPdfByChaptersController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SplitPdfByChaptersController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySectionsController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySectionsController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySizeController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySizeController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/ToSinglePageController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/ToSinglePageController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/UserController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/UserController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/index.html create mode 100644 testResult/html/stirling.software.SPDF.controller.api/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/AccountWebController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/AccountWebController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/ConverterWebController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/ConverterWebController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/DatabaseWebController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/DatabaseWebController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/GeneralWebController$1.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/GeneralWebController$FontResource.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/GeneralWebController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/GeneralWebController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/HomeWebController$1.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/HomeWebController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/HomeWebController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/MetricsController$EndpointCount.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/MetricsController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/MetricsController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/OtherWebController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/OtherWebController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/SecurityWebController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/SecurityWebController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/SignatureController.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/SignatureController.java.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/index.html create mode 100644 testResult/html/stirling.software.SPDF.controller.web/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.converters/index.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.converters/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.misc/HighContrastColorCombination.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.misc/HighContrastColorCombination.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.misc/OptimizePdfRequest.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.misc/OptimizePdfRequest.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.misc/ReplaceAndInvert.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.misc/ReplaceAndInvert.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.misc/index.html create mode 100644 testResult/html/stirling.software.SPDF.model.api.misc/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.model.api/PDFWithPageNums.html create mode 100644 testResult/html/stirling.software.SPDF.model.api/PDFWithPageNums.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.api/index.html create mode 100644 testResult/html/stirling.software.SPDF.model.api/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/BackupNotFoundException.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/BackupNotFoundException.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/NoProviderFoundException.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/NoProviderFoundException.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/UnsupportedProviderException.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/UnsupportedProviderException.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/UnsupportedUsernameAttribute.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/UnsupportedUsernameAttribute.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/index.html create mode 100644 testResult/html/stirling.software.SPDF.model.exception/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/GitHubProvider.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/GitHubProvider.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/GoogleProvider.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/GoogleProvider.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/KeycloakProvider.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/KeycloakProvider.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/Provider.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/Provider.java.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/index.html create mode 100644 testResult/html/stirling.software.SPDF.model.provider/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApiEndpoint.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApiEndpoint.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApiKeyAuthenticationToken.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApiKeyAuthenticationToken.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Driver.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$EnterpriseEdition$CustomMetadata.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$CustomMetadata.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$GoogleDrive.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$SessionLimit.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$TimeoutMinutes.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$LoginMethods.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2$Client.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$SAML2.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$System.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties$Ui.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties.html create mode 100644 testResult/html/stirling.software.SPDF.model/ApplicationProperties.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/AttemptCounter.html create mode 100644 testResult/html/stirling.software.SPDF.model/AttemptCounter.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/AuthenticationType.html create mode 100644 testResult/html/stirling.software.SPDF.model/AuthenticationType.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/Authority.html create mode 100644 testResult/html/stirling.software.SPDF.model/Authority.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/InputStreamTemplateResource.html create mode 100644 testResult/html/stirling.software.SPDF.model/InputStreamTemplateResource.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/PersistentLogin.html create mode 100644 testResult/html/stirling.software.SPDF.model/PersistentLogin.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/PipelineConfig.html create mode 100644 testResult/html/stirling.software.SPDF.model/PipelineConfig.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/PipelineOperation.html create mode 100644 testResult/html/stirling.software.SPDF.model/PipelineOperation.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/Role.html create mode 100644 testResult/html/stirling.software.SPDF.model/Role.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/SortTypes.html create mode 100644 testResult/html/stirling.software.SPDF.model/SortTypes.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/User.html create mode 100644 testResult/html/stirling.software.SPDF.model/User.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/UsernameAttribute.html create mode 100644 testResult/html/stirling.software.SPDF.model/UsernameAttribute.java.html create mode 100644 testResult/html/stirling.software.SPDF.model/index.html create mode 100644 testResult/html/stirling.software.SPDF.model/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.pdf/FlexibleCSVWriter.html create mode 100644 testResult/html/stirling.software.SPDF.pdf/FlexibleCSVWriter.java.html create mode 100644 testResult/html/stirling.software.SPDF.pdf/TextFinder$MatchInfo.html create mode 100644 testResult/html/stirling.software.SPDF.pdf/TextFinder.html create mode 100644 testResult/html/stirling.software.SPDF.pdf/TextFinder.java.html create mode 100644 testResult/html/stirling.software.SPDF.pdf/index.html create mode 100644 testResult/html/stirling.software.SPDF.pdf/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.repository/JPATokenRepositoryImpl.html create mode 100644 testResult/html/stirling.software.SPDF.repository/JPATokenRepositoryImpl.java.html create mode 100644 testResult/html/stirling.software.SPDF.repository/index.html create mode 100644 testResult/html/stirling.software.SPDF.repository/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.service.misc/ReplaceAndInvertColorService.html create mode 100644 testResult/html/stirling.software.SPDF.service.misc/ReplaceAndInvertColorService.java.html create mode 100644 testResult/html/stirling.software.SPDF.service.misc/index.html create mode 100644 testResult/html/stirling.software.SPDF.service.misc/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.service/CertificateValidationService.html create mode 100644 testResult/html/stirling.software.SPDF.service/CertificateValidationService.java.html create mode 100644 testResult/html/stirling.software.SPDF.service/CustomPDFDocumentFactory.html create mode 100644 testResult/html/stirling.software.SPDF.service/CustomPDFDocumentFactory.java.html create mode 100644 testResult/html/stirling.software.SPDF.service/LanguageService.html create mode 100644 testResult/html/stirling.software.SPDF.service/LanguageService.java.html create mode 100644 testResult/html/stirling.software.SPDF.service/MetricsAggregatorService.html create mode 100644 testResult/html/stirling.software.SPDF.service/MetricsAggregatorService.java.html create mode 100644 testResult/html/stirling.software.SPDF.service/PdfImageRemovalService.html create mode 100644 testResult/html/stirling.software.SPDF.service/PdfImageRemovalService.java.html create mode 100644 testResult/html/stirling.software.SPDF.service/PdfMetadataService.html create mode 100644 testResult/html/stirling.software.SPDF.service/PdfMetadataService.java.html create mode 100644 testResult/html/stirling.software.SPDF.service/PostHogService.html create mode 100644 testResult/html/stirling.software.SPDF.service/PostHogService.java.html create mode 100644 testResult/html/stirling.software.SPDF.service/SignatureService.html create mode 100644 testResult/html/stirling.software.SPDF.service/SignatureService.java.html create mode 100644 testResult/html/stirling.software.SPDF.service/index.html create mode 100644 testResult/html/stirling.software.SPDF.service/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/CustomColorReplaceStrategy.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/CustomColorReplaceStrategy.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/HighContrastColorReplaceDecider.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/HighContrastColorReplaceDecider.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/InvertFullColorStrategy.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/InvertFullColorStrategy.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/PdfTextStripperCustom.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/PdfTextStripperCustom.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/ReplaceAndInvertColorStrategy.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/ReplaceAndInvertColorStrategy.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/index.html create mode 100644 testResult/html/stirling.software.SPDF.utils.misc/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor$1.html create mode 100644 testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor.html create mode 100644 testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor$1.html create mode 100644 testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor.html create mode 100644 testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils.propertyeditor/index.html create mode 100644 testResult/html/stirling.software.SPDF.utils.propertyeditor/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.utils.validation/Validator.html create mode 100644 testResult/html/stirling.software.SPDF.utils.validation/Validator.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils.validation/index.html create mode 100644 testResult/html/stirling.software.SPDF.utils.validation/index.source.html create mode 100644 testResult/html/stirling.software.SPDF.utils/CheckProgramInstall.html create mode 100644 testResult/html/stirling.software.SPDF.utils/CheckProgramInstall.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/CustomHtmlSanitizer.html create mode 100644 testResult/html/stirling.software.SPDF.utils/CustomHtmlSanitizer.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/ErrorUtils.html create mode 100644 testResult/html/stirling.software.SPDF.utils/ErrorUtils.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/FileInfo.html create mode 100644 testResult/html/stirling.software.SPDF.utils/FileInfo.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/FileMonitor.html create mode 100644 testResult/html/stirling.software.SPDF.utils/FileMonitor.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/FileToPdf$1.html create mode 100644 testResult/html/stirling.software.SPDF.utils/FileToPdf.html create mode 100644 testResult/html/stirling.software.SPDF.utils/FileToPdf.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/GeneralUtils$1.html create mode 100644 testResult/html/stirling.software.SPDF.utils/GeneralUtils.html create mode 100644 testResult/html/stirling.software.SPDF.utils/GeneralUtils.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/ImageProcessingUtils.html create mode 100644 testResult/html/stirling.software.SPDF.utils/ImageProcessingUtils.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/PDFToFile.html create mode 100644 testResult/html/stirling.software.SPDF.utils/PDFToFile.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/PdfUtils$PdfImageDimensionValue.html create mode 100644 testResult/html/stirling.software.SPDF.utils/PdfUtils$PdfRenderSettingsKey.html create mode 100644 testResult/html/stirling.software.SPDF.utils/PdfUtils.html create mode 100644 testResult/html/stirling.software.SPDF.utils/PdfUtils.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/ProcessExecutor$ProcessExecutorResult.html create mode 100644 testResult/html/stirling.software.SPDF.utils/ProcessExecutor$Processes.html create mode 100644 testResult/html/stirling.software.SPDF.utils/ProcessExecutor.html create mode 100644 testResult/html/stirling.software.SPDF.utils/ProcessExecutor.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/PropertyConfigs.html create mode 100644 testResult/html/stirling.software.SPDF.utils/PropertyConfigs.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/RequestUriUtils.html create mode 100644 testResult/html/stirling.software.SPDF.utils/RequestUriUtils.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/UIScaling.html create mode 100644 testResult/html/stirling.software.SPDF.utils/UIScaling.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/UrlUtils.html create mode 100644 testResult/html/stirling.software.SPDF.utils/UrlUtils.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/WebResponseUtils.html create mode 100644 testResult/html/stirling.software.SPDF.utils/WebResponseUtils.java.html create mode 100644 testResult/html/stirling.software.SPDF.utils/index.html create mode 100644 testResult/html/stirling.software.SPDF.utils/index.source.html create mode 100644 testResult/html/stirling.software.SPDF/LibreOfficeListener.html create mode 100644 testResult/html/stirling.software.SPDF/LibreOfficeListener.java.html create mode 100644 testResult/html/stirling.software.SPDF/SPDFApplication.html create mode 100644 testResult/html/stirling.software.SPDF/SPDFApplication.java.html create mode 100644 testResult/html/stirling.software.SPDF/index.html create mode 100644 testResult/html/stirling.software.SPDF/index.source.html diff --git a/build.gradle b/build.gradle index 6e0f07741..7498c62d8 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ plugins { //id "nebula.lint" version "19.0.3" id("org.panteleyev.jpackageplugin") version "1.6.1" id "org.sonarqube" version "6.1.0.5360" + id "jacoco" } import com.github.jk1.license.render.* @@ -363,7 +364,7 @@ launch4j { spotless { java { - target project.fileTree('src').include('**/*.java') + target project.fileTree('src').include('**/*.java').exclude('**/test/**') googleJavaFormat("1.25.2").aosp().reorderImports(false) @@ -588,19 +589,36 @@ task printMacVersion { tasks.named('generateOpenApiDocs') { doNotTrackState("Tracking state is not supported for this task") } -tasks.register('convertersTest', Test) { - group = 'verification' - description = 'Run only converter integration tests under controller/api/converters' - - // 指定来自 test 源集的字节码和 classpath - testClassesDirs = sourceSets.test.get().output.classesDirs - classpath = sourceSets.test.get().runtimeClasspath - - // 只包含 controller/api/converters 目录下的测试类 - include '**/stirling/software/SPDF/controller/api/converters/ConvertMarkdownToPdfIntegrationTest.java' -} // 确保默认 test 任务还是用 JUnit Platform tasks.named('test') { useJUnitPlatform() } + +tasks.register('convertersTest', Test) { + group = 'verification' + description = 'Run only markdown/pdf converter unit tests' + useJUnitPlatform() + + // 只包含 markdown/pdf 的单元测试类 + include '**/ConvertMarkdownToPdfTest.class' + include '**/ConvertPDFToMarkdownTest.class' + + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath +} + +jacocoTestReport { + dependsOn convertersTest // 关键:只用你定义的任务来生成覆盖率 + + reports { + html.required = true + xml.required = true + csv.required = false + } + + executionData fileTree(buildDir).include( + "/jacoco/convertersTest.exec" + ) +} + diff --git a/src/test/java/stirling/software/SPDF/utils/ConvertMarkdownToPdfIntegrationTest.java b/src/test/java/stirling/software/SPDF/utils/ConvertMarkdownToPdfIntegrationTest.java index 3631df488..8c00bfa48 100644 --- a/src/test/java/stirling/software/SPDF/utils/ConvertMarkdownToPdfIntegrationTest.java +++ b/src/test/java/stirling/software/SPDF/utils/ConvertMarkdownToPdfIntegrationTest.java @@ -3,7 +3,6 @@ package stirling.software.SPDF.utils; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import net.bytebuddy.implementation.bytecode.Throw; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -13,8 +12,6 @@ import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.MockMvc; -import java.io.IOException; - @SpringBootTest( webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { diff --git a/src/test/java/stirling/software/SPDF/utils/FileToPdfTest.java b/src/test/java/stirling/software/SPDF/utils/FileToPdfTest.java deleted file mode 100644 index 38e2ec3b8..000000000 --- a/src/test/java/stirling/software/SPDF/utils/FileToPdfTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package stirling.software.SPDF.utils; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.IOException; - -import org.junit.jupiter.api.Test; - -import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest; - -public class FileToPdfTest { - - /** - * Test the HTML to PDF conversion. This test expects an IOException when an empty HTML input is - * provided. - */ - @Test - public void testConvertHtmlToPdf() { - HTMLToPdfRequest request = new HTMLToPdfRequest(); - byte[] fileBytes = new byte[0]; // Sample file bytes (empty input) - String fileName = "test.html"; // Sample file name indicating an HTML file - boolean disableSanitize = false; // Flag to control sanitization - - // Expect an IOException to be thrown due to empty input - Throwable thrown = - assertThrows( - IOException.class, - () -> - FileToPdf.convertHtmlToPdf( - "/path/", request, fileBytes, fileName, disableSanitize)); - assertNotNull(thrown); - } - - /** - * Test sanitizeZipFilename with null or empty input. It should return an empty string in these - * cases. - */ - @Test - public void testSanitizeZipFilename_NullOrEmpty() { - assertEquals("", FileToPdf.sanitizeZipFilename(null)); - assertEquals("", FileToPdf.sanitizeZipFilename(" ")); - } - - /** - * Test sanitizeZipFilename to ensure it removes path traversal sequences. This includes - * removing both forward and backward slash sequences. - */ - @Test - public void testSanitizeZipFilename_RemovesTraversalSequences() { - String input = "../some/../path/..\\to\\file.txt"; - String expected = "some/path/to/file.txt"; - - // Print output for debugging purposes - System.out.println("sanitizeZipFilename " + FileToPdf.sanitizeZipFilename(input)); - System.out.flush(); - - // Expect that the method replaces backslashes with forward slashes - // and removes path traversal sequences - assertEquals(expected, FileToPdf.sanitizeZipFilename(input)); - } - - /** Test sanitizeZipFilename to ensure that it removes leading drive letters and slashes. */ - @Test - public void testSanitizeZipFilename_RemovesLeadingDriveAndSlashes() { - String input = "C:\\folder\\file.txt"; - String expected = "folder/file.txt"; - assertEquals(expected, FileToPdf.sanitizeZipFilename(input)); - - input = "/folder/file.txt"; - expected = "folder/file.txt"; - assertEquals(expected, FileToPdf.sanitizeZipFilename(input)); - } - - /** Test sanitizeZipFilename to verify that safe filenames remain unchanged. */ - @Test - public void testSanitizeZipFilename_NoChangeForSafeNames() { - String input = "folder/subfolder/file.txt"; - assertEquals(input, FileToPdf.sanitizeZipFilename(input)); - } -} diff --git a/testResult/html/index.html b/testResult/html/index.html new file mode 100644 index 000000000..a5fc74f57 --- /dev/null +++ b/testResult/html/index.html @@ -0,0 +1 @@ +Stirling-PDF

Stirling-PDF

ElementMissed InstructionsCov.Missed BranchesCov.MissedCxtyMissedLinesMissedMethodsMissedClasses
Total48,711 of 49,5151%4,460 of 4,5020%3,7893,83211,92312,1361,4881,521235251
stirling.software.SPDF.controller.api.misc6,7710%5340%4024021,5841,5841231232222
stirling.software.SPDF.controller.api5,9760%4540%3373371,4351,4351031031919
stirling.software.SPDF.utils4,46657611%500326%4024241,0811,2291301451722
stirling.software.SPDF.controller.api.security4,4250%3630%2382381,0371,037545499
stirling.software.SPDF.config4,187591%4230%3763829891,0071641702427
stirling.software.SPDF.config.security3,4140%4070%3353358668661311311515
stirling.software.SPDF.controller.web2,6570%2690%2932937177171421421313
stirling.software.SPDF.service2,6240%2080%195196631633919278
stirling.software.SPDF.controller.api.pipeline2,5080%2080%165165550550616199
stirling.software.SPDF.UI.impl1,5770%960%9696429429464688
stirling.software.SPDF.model1,4722%1541%2162203183261371412327
stirling.software.SPDF.EE1,3970%1660%115115385385323244
stirling.software.SPDF.controller.api.converters1,3941127%12375%971053473803540911
stirling.software.SPDF.config.security.saml29220%880%7272219219282866
stirling.software.SPDF.config.security.oauth28080%840%6262232232202044
stirling.software.SPDF.config.security.database7370%710%6363206206272733
stirling.software.SPDF.utils.misc5560%340%3030145145131355
stirling.software.SPDF5250%560%4646147147181822
org.apache.pdfbox.examples.signature5000%340%3838138138212144
stirling.software.SPDF.config.security.session4030%420%4646109109252555
stirling.software.SPDF.model.provider3700%480%53539292282844
stirling.software.SPDF.controller.api.filters3420%520%373786867711
stirling.software.SPDF.pdf2200%140%151550508833
stirling.software.SPDF.utils.propertyeditor1000%0%9926267744
org.apache.pdfbox.examples.util940%0%14143030131322
stirling.software.SPDF.repository850%0%7725255511
stirling.software.SPDF.model.api.misc0%n/a3310103333
stirling.software.SPDF.utils.validation0%160%121212124411
stirling.software.SPDF.Factories0%60%55662211
stirling.software.SPDF.model.exception0%n/a5510105544
stirling.software.SPDF.service.misc0%n/a22662211
stirling.software.SPDF.model.api0%n/a22442211
stirling.software.SPDF.config.interfaces0%n/a11111111
stirling.software.SPDF.model.api.converters100%n/a02040201
\ No newline at end of file diff --git a/testResult/html/jacoco-resources/branchfc.gif b/testResult/html/jacoco-resources/branchfc.gif new file mode 100644 index 0000000000000000000000000000000000000000..989b46d30469b56b014758f846ee6c5abfda16aa GIT binary patch literal 91 zcmZ?wbhEHb6=b<*h$V|V6X-NwhSNb literal 0 HcmV?d00001 diff --git a/testResult/html/jacoco-resources/branchnc.gif b/testResult/html/jacoco-resources/branchnc.gif new file mode 100644 index 0000000000000000000000000000000000000000..1933e07c376bb71bdd9aac91cf858da3fcdb0f1c GIT binary patch literal 91 zcmZ?wbhEHb6=b<*h$V|V6X-N9U38B literal 0 HcmV?d00001 diff --git a/testResult/html/jacoco-resources/branchpc.gif b/testResult/html/jacoco-resources/branchpc.gif new file mode 100644 index 0000000000000000000000000000000000000000..cbf711b7030929b733f22f7a0cf3dbf61fe7868f GIT binary patch literal 91 zcmZ?wbhEHbm$mi>nCYN#As;!%lJz1A{dHmlPuc literal 0 HcmV?d00001 diff --git a/testResult/html/jacoco-resources/bundle.gif b/testResult/html/jacoco-resources/bundle.gif new file mode 100644 index 0000000000000000000000000000000000000000..fca9c53e629a7a5c07186ac1e2a1e37d8d6e88f4 GIT binary patch literal 709 zcmZ?wbhEHb6krfwxXQrrpW*-7BK;o8CDEUD?$vun5^UNelT%D!ODhRsX(Ohwq+z^!{nkw1lu( zDPc2HV&`P7KEHX-jYA>R6T@ewM9fTyo0E0x)!k_2wz@P-Sk{|^LE{K>+|z);Vi!vF-J zIALI4-caAv+|t_C-oY&>$uA|y-ND80=rPrik*keM);A(7JS@bMXJ#`uzjsjN>eYc> zj1!vJoq|_~`Ugb%`8WwRvs$=Bx;h_qcXM-KZDthLjMNep5fPP;Q{vk%FCD3^prRsd zAfR@-Nl4k$GSW~(G16XNhoM=9$H>NPjk%o(&&DPp6ODz*?)|b>X&fF28jY>Ox-nZU Y5*r^bWMyL$kZ52~Skzz7#K>R`0G8r7i~s-t literal 0 HcmV?d00001 diff --git a/testResult/html/jacoco-resources/down.gif b/testResult/html/jacoco-resources/down.gif new file mode 100644 index 0000000000000000000000000000000000000000..440a14db74e76c2b6e854eacac1c44414b166271 GIT binary patch literal 67 zcmZ?wbhEHbZ%p}jXB Ub$^Lu-Ncq(ygK&ScM%3_0Po}%Qvd(} literal 0 HcmV?d00001 diff --git a/testResult/html/jacoco-resources/greenbar.gif b/testResult/html/jacoco-resources/greenbar.gif new file mode 100644 index 0000000000000000000000000000000000000000..0ba65672530ee09f086821a26156836d0c91bd74 GIT binary patch literal 91 zcmZ?wbhEHbWMtrCc+ADXzmZ>do2<@m9j_x^v8Q5duh#b5>RIq$!Lmoo);w9mu$BQ0 eDgI<(1nOeYVE_V<84N5O20cYWMlKB;4AuaIXBwOU literal 0 HcmV?d00001 diff --git a/testResult/html/jacoco-resources/group.gif b/testResult/html/jacoco-resources/group.gif new file mode 100644 index 0000000000000000000000000000000000000000..a4ea580d278fb727e4ae692838877fa63c4becf9 GIT binary patch literal 351 zcmZ?wbhEHb6krfwxXQpVwXtJrV`pb|Z&Bgo_>{Q`Df1G5Wa`}H^qKLgbHn221;#86 zie2Oyy23SVg;&(l)`=%9{nuIstg#PSrQx<&&vS#m*G7G>4W@o;CvAN*Y1^AgTVGGw z_ImEoPjiobns@ZmyknnMUi-Q7>W`Jzer$aB_t(pL-|kQQ|MAfO*PGv5?Ee3B$^ToO z|A8VGOaEW3eSEO?=BC06Ybq|Tt-P?N@;?|b;0205Sr{1@Oc``Qsz82XV5>PWtH47? zs^4Q~P@BxTjDV;&5*!R(s==>VnJe}-&SEIintfiq!@CwnVRxXubL!4|)qjO}gg>klxZ?TGXw~#-V zU_Y2&N}FX?r*L1YbYiM-aj|xBv2}#Mgo3?-guaA=wSS1Yfrz+)iMWB7#*ml2h^x<; ztIwFU(w+bR{{R30A^8LW0015UEC2ui01yBW000F(peK%GX`X1Rt}L1aL$Vf5mpMgx vG+WO#2NYmJDM}^)l;8n@L?90V%CN9pFcyU&MPO(u48jTlL$uClRtNw)MiWcq literal 0 HcmV?d00001 diff --git a/testResult/html/jacoco-resources/package.gif b/testResult/html/jacoco-resources/package.gif new file mode 100644 index 0000000000000000000000000000000000000000..131c28da405493661e3253ef79a68bd273039295 GIT binary patch literal 227 zcmZ?wbhEHb6krfwIKsg2^W*Nf7neOfxp04z;n8NJ+xzDotkS){bH@Hst%K#-*LO_c zo~yCDQ0v_4?v)A3lSAd#C95utQCbkGxF}NT_=2WF8}WGs5taT9|NsAIzy=h5vM@3* zNHFMtBtdpEuqG&|^`&Ia(}-MpBVo@mW@+b{B25<}cFdc?!Kkoc14n0vkh1`XOwU>7 z#al8o_@;D=?hdfkdC)D9Q@O@%Lfqp;ZBt~9C*29`GMF2XzQp8akWQVjDvMC75PzEx Mi%z;upCW@b03m@=3jhEB literal 0 HcmV?d00001 diff --git a/testResult/html/jacoco-resources/prettify.css b/testResult/html/jacoco-resources/prettify.css new file mode 100644 index 000000000..be5166e0f --- /dev/null +++ b/testResult/html/jacoco-resources/prettify.css @@ -0,0 +1,13 @@ +/* Pretty printing styles. Used with prettify.js. */ + +.str { color: #2A00FF; } +.kwd { color: #7F0055; font-weight:bold; } +.com { color: #3F5FBF; } +.typ { color: #606; } +.lit { color: #066; } +.pun { color: #660; } +.pln { color: #000; } +.tag { color: #008; } +.atn { color: #606; } +.atv { color: #080; } +.dec { color: #606; } diff --git a/testResult/html/jacoco-resources/prettify.js b/testResult/html/jacoco-resources/prettify.js new file mode 100644 index 000000000..b2766fe0a --- /dev/null +++ b/testResult/html/jacoco-resources/prettify.js @@ -0,0 +1,1510 @@ +// Copyright (C) 2006 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** + * @fileoverview + * some functions for browser-side pretty printing of code contained in html. + *

+ * + * For a fairly comprehensive set of languages see the + * README + * file that came with this source. At a minimum, the lexer should work on a + * number of languages including C and friends, Java, Python, Bash, SQL, HTML, + * XML, CSS, Javascript, and Makefiles. It works passably on Ruby, PHP and Awk + * and a subset of Perl, but, because of commenting conventions, doesn't work on + * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class. + *

+ * Usage:

    + *
  1. include this source file in an html page via + * {@code } + *
  2. define style rules. See the example page for examples. + *
  3. mark the {@code
    } and {@code } tags in your source with
    + *    {@code class=prettyprint.}
    + *    You can also use the (html deprecated) {@code } tag, but the pretty
    + *    printer needs to do more substantial DOM manipulations to support that, so
    + *    some css styles may not be preserved.
    + * </ol>
    + * That's it.  I wanted to keep the API as simple as possible, so there's no
    + * need to specify which language the code is in, but if you wish, you can add
    + * another class to the {@code <pre>} or {@code <code>} element to specify the
    + * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
    + * starts with "lang-" followed by a file extension, specifies the file type.
    + * See the "lang-*.js" files in this directory for code that implements
    + * per-language file handlers.
    + * <p>
    + * Change log:<br>
    + * cbeust, 2006/08/22
    + * <blockquote>
    + *   Java annotations (start with "@") are now captured as literals ("lit")
    + * </blockquote>
    + * @requires console
    + */
    +
    +// JSLint declarations
    +/*global console, document, navigator, setTimeout, window */
    +
    +/**
    + * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
    + * UI events.
    + * If set to {@code false}, {@code prettyPrint()} is synchronous.
    + */
    +window['PR_SHOULD_USE_CONTINUATION'] = true;
    +
    +/** the number of characters between tab columns */
    +window['PR_TAB_WIDTH'] = 8;
    +
    +/** Walks the DOM returning a properly escaped version of innerHTML.
    +  * @param {Node} node
    +  * @param {Array.<string>} out output buffer that receives chunks of HTML.
    +  */
    +window['PR_normalizedHtml']
    +
    +/** Contains functions for creating and registering new language handlers.
    +  * @type {Object}
    +  */
    +  = window['PR']
    +
    +/** Pretty print a chunk of code.
    +  *
    +  * @param {string} sourceCodeHtml code as html
    +  * @return {string} code as html, but prettier
    +  */
    +  = window['prettyPrintOne']
    +/** Find all the {@code <pre>} and {@code <code>} tags in the DOM with
    +  * {@code class=prettyprint} and prettify them.
    +  * @param {Function?} opt_whenDone if specified, called when the last entry
    +  *     has been finished.
    +  */
    +  = window['prettyPrint'] = void 0;
    +
    +/** browser detection. @extern @returns false if not IE, otherwise the major version. */
    +window['_pr_isIE6'] = function () {
    +  var ieVersion = navigator && navigator.userAgent &&
    +      navigator.userAgent.match(/\bMSIE ([678])\./);
    +  ieVersion = ieVersion ? +ieVersion[1] : false;
    +  window['_pr_isIE6'] = function () { return ieVersion; };
    +  return ieVersion;
    +};
    +
    +
    +(function () {
    +  // Keyword lists for various languages.
    +  var FLOW_CONTROL_KEYWORDS =
    +      "break continue do else for if return while ";
    +  var C_KEYWORDS = FLOW_CONTROL_KEYWORDS + "auto case char const default " +
    +      "double enum extern float goto int long register short signed sizeof " +
    +      "static struct switch typedef union unsigned void volatile ";
    +  var COMMON_KEYWORDS = C_KEYWORDS + "catch class delete false import " +
    +      "new operator private protected public this throw true try typeof ";
    +  var CPP_KEYWORDS = COMMON_KEYWORDS + "alignof align_union asm axiom bool " +
    +      "concept concept_map const_cast constexpr decltype " +
    +      "dynamic_cast explicit export friend inline late_check " +
    +      "mutable namespace nullptr reinterpret_cast static_assert static_cast " +
    +      "template typeid typename using virtual wchar_t where ";
    +  var JAVA_KEYWORDS = COMMON_KEYWORDS +
    +      "abstract boolean byte extends final finally implements import " +
    +      "instanceof null native package strictfp super synchronized throws " +
    +      "transient ";
    +  var CSHARP_KEYWORDS = JAVA_KEYWORDS +
    +      "as base by checked decimal delegate descending event " +
    +      "fixed foreach from group implicit in interface internal into is lock " +
    +      "object out override orderby params partial readonly ref sbyte sealed " +
    +      "stackalloc string select uint ulong unchecked unsafe ushort var ";
    +  var JSCRIPT_KEYWORDS = COMMON_KEYWORDS +
    +      "debugger eval export function get null set undefined var with " +
    +      "Infinity NaN ";
    +  var PERL_KEYWORDS = "caller delete die do dump elsif eval exit foreach for " +
    +      "goto if import last local my next no our print package redo require " +
    +      "sub undef unless until use wantarray while BEGIN END ";
    +  var PYTHON_KEYWORDS = FLOW_CONTROL_KEYWORDS + "and as assert class def del " +
    +      "elif except exec finally from global import in is lambda " +
    +      "nonlocal not or pass print raise try with yield " +
    +      "False True None ";
    +  var RUBY_KEYWORDS = FLOW_CONTROL_KEYWORDS + "alias and begin case class def" +
    +      " defined elsif end ensure false in module next nil not or redo rescue " +
    +      "retry self super then true undef unless until when yield BEGIN END ";
    +  var SH_KEYWORDS = FLOW_CONTROL_KEYWORDS + "case done elif esac eval fi " +
    +      "function in local set then until ";
    +  var ALL_KEYWORDS = (
    +      CPP_KEYWORDS + CSHARP_KEYWORDS + JSCRIPT_KEYWORDS + PERL_KEYWORDS +
    +      PYTHON_KEYWORDS + RUBY_KEYWORDS + SH_KEYWORDS);
    +
    +  // token style names.  correspond to css classes
    +  /** token style for a string literal */
    +  var PR_STRING = 'str';
    +  /** token style for a keyword */
    +  var PR_KEYWORD = 'kwd';
    +  /** token style for a comment */
    +  var PR_COMMENT = 'com';
    +  /** token style for a type */
    +  var PR_TYPE = 'typ';
    +  /** token style for a literal value.  e.g. 1, null, true. */
    +  var PR_LITERAL = 'lit';
    +  /** token style for a punctuation string. */
    +  var PR_PUNCTUATION = 'pun';
    +  /** token style for a punctuation string. */
    +  var PR_PLAIN = 'pln';
    +
    +  /** token style for an sgml tag. */
    +  var PR_TAG = 'tag';
    +  /** token style for a markup declaration such as a DOCTYPE. */
    +  var PR_DECLARATION = 'dec';
    +  /** token style for embedded source. */
    +  var PR_SOURCE = 'src';
    +  /** token style for an sgml attribute name. */
    +  var PR_ATTRIB_NAME = 'atn';
    +  /** token style for an sgml attribute value. */
    +  var PR_ATTRIB_VALUE = 'atv';
    +
    +  /**
    +   * A class that indicates a section of markup that is not code, e.g. to allow
    +   * embedding of line numbers within code listings.
    +   */
    +  var PR_NOCODE = 'nocode';
    +
    +  /** A set of tokens that can precede a regular expression literal in
    +    * javascript.
    +    * http://www.mozilla.org/js/language/js20/rationale/syntax.html has the full
    +    * list, but I've removed ones that might be problematic when seen in
    +    * languages that don't support regular expression literals.
    +    *
    +    * <p>Specifically, I've removed any keywords that can't precede a regexp
    +    * literal in a syntactically legal javascript program, and I've removed the
    +    * "in" keyword since it's not a keyword in many languages, and might be used
    +    * as a count of inches.
    +    *
    +    * <p>The link a above does not accurately describe EcmaScript rules since
    +    * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
    +    * very well in practice.
    +    *
    +    * @private
    +    */
    +  var REGEXP_PRECEDER_PATTERN = function () {
    +      var preceders = [
    +          "!", "!=", "!==", "#", "%", "%=", "&", "&&", "&&=",
    +          "&=", "(", "*", "*=", /* "+", */ "+=", ",", /* "-", */ "-=",
    +          "->", /*".", "..", "...", handled below */ "/", "/=", ":", "::", ";",
    +          "<", "<<", "<<=", "<=", "=", "==", "===", ">",
    +          ">=", ">>", ">>=", ">>>", ">>>=", "?", "@", "[",
    +          "^", "^=", "^^", "^^=", "{", "|", "|=", "||",
    +          "||=", "~" /* handles =~ and !~ */,
    +          "break", "case", "continue", "delete",
    +          "do", "else", "finally", "instanceof",
    +          "return", "throw", "try", "typeof"
    +          ];
    +      var pattern = '(?:^^|[+-]';
    +      for (var i = 0; i < preceders.length; ++i) {
    +        pattern += '|' + preceders[i].replace(/([^=<>:&a-z])/g, '\\$1');
    +      }
    +      pattern += ')\\s*';  // matches at end, and matches empty string
    +      return pattern;
    +      // CAVEAT: this does not properly handle the case where a regular
    +      // expression immediately follows another since a regular expression may
    +      // have flags for case-sensitivity and the like.  Having regexp tokens
    +      // adjacent is not valid in any language I'm aware of, so I'm punting.
    +      // TODO: maybe style special characters inside a regexp as punctuation.
    +    }();
    +
    +  // Define regexps here so that the interpreter doesn't have to create an
    +  // object each time the function containing them is called.
    +  // The language spec requires a new object created even if you don't access
    +  // the $1 members.
    +  var pr_amp = /&/g;
    +  var pr_lt = /</g;
    +  var pr_gt = />/g;
    +  var pr_quot = /\"/g;
    +  /** like textToHtml but escapes double quotes to be attribute safe. */
    +  function attribToHtml(str) {
    +    return str.replace(pr_amp, '&amp;')
    +        .replace(pr_lt, '&lt;')
    +        .replace(pr_gt, '&gt;')
    +        .replace(pr_quot, '&quot;');
    +  }
    +
    +  /** escapest html special characters to html. */
    +  function textToHtml(str) {
    +    return str.replace(pr_amp, '&amp;')
    +        .replace(pr_lt, '&lt;')
    +        .replace(pr_gt, '&gt;');
    +  }
    +
    +
    +  var pr_ltEnt = /&lt;/g;
    +  var pr_gtEnt = /&gt;/g;
    +  var pr_aposEnt = /&apos;/g;
    +  var pr_quotEnt = /&quot;/g;
    +  var pr_ampEnt = /&amp;/g;
    +  var pr_nbspEnt = /&nbsp;/g;
    +  /** unescapes html to plain text. */
    +  function htmlToText(html) {
    +    var pos = html.indexOf('&');
    +    if (pos < 0) { return html; }
    +    // Handle numeric entities specially.  We can't use functional substitution
    +    // since that doesn't work in older versions of Safari.
    +    // These should be rare since most browsers convert them to normal chars.
    +    for (--pos; (pos = html.indexOf('&#', pos + 1)) >= 0;) {
    +      var end = html.indexOf(';', pos);
    +      if (end >= 0) {
    +        var num = html.substring(pos + 3, end);
    +        var radix = 10;
    +        if (num && num.charAt(0) === 'x') {
    +          num = num.substring(1);
    +          radix = 16;
    +        }
    +        var codePoint = parseInt(num, radix);
    +        if (!isNaN(codePoint)) {
    +          html = (html.substring(0, pos) + String.fromCharCode(codePoint) +
    +                  html.substring(end + 1));
    +        }
    +      }
    +    }
    +
    +    return html.replace(pr_ltEnt, '<')
    +        .replace(pr_gtEnt, '>')
    +        .replace(pr_aposEnt, "'")
    +        .replace(pr_quotEnt, '"')
    +        .replace(pr_nbspEnt, ' ')
    +        .replace(pr_ampEnt, '&');
    +  }
    +
    +  /** is the given node's innerHTML normally unescaped? */
    +  function isRawContent(node) {
    +    return 'XMP' === node.tagName;
    +  }
    +
    +  var newlineRe = /[\r\n]/g;
    +  /**
    +   * Are newlines and adjacent spaces significant in the given node's innerHTML?
    +   */
    +  function isPreformatted(node, content) {
    +    // PRE means preformatted, and is a very common case, so don't create
    +    // unnecessary computed style objects.
    +    if ('PRE' === node.tagName) { return true; }
    +    if (!newlineRe.test(content)) { return true; }  // Don't care
    +    var whitespace = '';
    +    // For disconnected nodes, IE has no currentStyle.
    +    if (node.currentStyle) {
    +      whitespace = node.currentStyle.whiteSpace;
    +    } else if (window.getComputedStyle) {
    +      // Firefox makes a best guess if node is disconnected whereas Safari
    +      // returns the empty string.
    +      whitespace = window.getComputedStyle(node, null).whiteSpace;
    +    }
    +    return !whitespace || whitespace === 'pre';
    +  }
    +
    +  function normalizedHtml(node, out, opt_sortAttrs) {
    +    switch (node.nodeType) {
    +      case 1:  // an element
    +        var name = node.tagName.toLowerCase();
    +
    +        out.push('<', name);
    +        var attrs = node.attributes;
    +        var n = attrs.length;
    +        if (n) {
    +          if (opt_sortAttrs) {
    +            var sortedAttrs = [];
    +            for (var i = n; --i >= 0;) { sortedAttrs[i] = attrs[i]; }
    +            sortedAttrs.sort(function (a, b) {
    +                return (a.name < b.name) ? -1 : a.name === b.name ? 0 : 1;
    +              });
    +            attrs = sortedAttrs;
    +          }
    +          for (var i = 0; i < n; ++i) {
    +            var attr = attrs[i];
    +            if (!attr.specified) { continue; }
    +            out.push(' ', attr.name.toLowerCase(),
    +                     '="', attribToHtml(attr.value), '"');
    +          }
    +        }
    +        out.push('>');
    +        for (var child = node.firstChild; child; child = child.nextSibling) {
    +          normalizedHtml(child, out, opt_sortAttrs);
    +        }
    +        if (node.firstChild || !/^(?:br|link|img)$/.test(name)) {
    +          out.push('<\/', name, '>');
    +        }
    +        break;
    +      case 3: case 4: // text
    +        out.push(textToHtml(node.nodeValue));
    +        break;
    +    }
    +  }
    +
    +  /**
    +   * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
    +   * matches the union o the sets o strings matched d by the input RegExp.
    +   * Since it matches globally, if the input strings have a start-of-input
    +   * anchor (/^.../), it is ignored for the purposes of unioning.
    +   * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
    +   * @return {RegExp} a global regex.
    +   */
    +  function combinePrefixPatterns(regexs) {
    +    var capturedGroupIndex = 0;
    +
    +    var needToFoldCase = false;
    +    var ignoreCase = false;
    +    for (var i = 0, n = regexs.length; i < n; ++i) {
    +      var regex = regexs[i];
    +      if (regex.ignoreCase) {
    +        ignoreCase = true;
    +      } else if (/[a-z]/i.test(regex.source.replace(
    +                     /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
    +        needToFoldCase = true;
    +        ignoreCase = false;
    +        break;
    +      }
    +    }
    +
    +    function decodeEscape(charsetPart) {
    +      if (charsetPart.charAt(0) !== '\\') { return charsetPart.charCodeAt(0); }
    +      switch (charsetPart.charAt(1)) {
    +        case 'b': return 8;
    +        case 't': return 9;
    +        case 'n': return 0xa;
    +        case 'v': return 0xb;
    +        case 'f': return 0xc;
    +        case 'r': return 0xd;
    +        case 'u': case 'x':
    +          return parseInt(charsetPart.substring(2), 16)
    +              || charsetPart.charCodeAt(1);
    +        case '0': case '1': case '2': case '3': case '4':
    +        case '5': case '6': case '7':
    +          return parseInt(charsetPart.substring(1), 8);
    +        default: return charsetPart.charCodeAt(1);
    +      }
    +    }
    +
    +    function encodeEscape(charCode) {
    +      if (charCode < 0x20) {
    +        return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
    +      }
    +      var ch = String.fromCharCode(charCode);
    +      if (ch === '\\' || ch === '-' || ch === '[' || ch === ']') {
    +        ch = '\\' + ch;
    +      }
    +      return ch;
    +    }
    +
    +    function caseFoldCharset(charSet) {
    +      var charsetParts = charSet.substring(1, charSet.length - 1).match(
    +          new RegExp(
    +              '\\\\u[0-9A-Fa-f]{4}'
    +              + '|\\\\x[0-9A-Fa-f]{2}'
    +              + '|\\\\[0-3][0-7]{0,2}'
    +              + '|\\\\[0-7]{1,2}'
    +              + '|\\\\[\\s\\S]'
    +              + '|-'
    +              + '|[^-\\\\]',
    +              'g'));
    +      var groups = [];
    +      var ranges = [];
    +      var inverse = charsetParts[0] === '^';
    +      for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
    +        var p = charsetParts[i];
    +        switch (p) {
    +          case '\\B': case '\\b':
    +          case '\\D': case '\\d':
    +          case '\\S': case '\\s':
    +          case '\\W': case '\\w':
    +            groups.push(p);
    +            continue;
    +        }
    +        var start = decodeEscape(p);
    +        var end;
    +        if (i + 2 < n && '-' === charsetParts[i + 1]) {
    +          end = decodeEscape(charsetParts[i + 2]);
    +          i += 2;
    +        } else {
    +          end = start;
    +        }
    +        ranges.push([start, end]);
    +        // If the range might intersect letters, then expand it.
    +        if (!(end < 65 || start > 122)) {
    +          if (!(end < 65 || start > 90)) {
    +            ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
    +          }
    +          if (!(end < 97 || start > 122)) {
    +            ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
    +          }
    +        }
    +      }
    +
    +      // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
    +      // -> [[1, 12], [14, 14], [16, 17]]
    +      ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
    +      var consolidatedRanges = [];
    +      var lastRange = [NaN, NaN];
    +      for (var i = 0; i < ranges.length; ++i) {
    +        var range = ranges[i];
    +        if (range[0] <= lastRange[1] + 1) {
    +          lastRange[1] = Math.max(lastRange[1], range[1]);
    +        } else {
    +          consolidatedRanges.push(lastRange = range);
    +        }
    +      }
    +
    +      var out = ['['];
    +      if (inverse) { out.push('^'); }
    +      out.push.apply(out, groups);
    +      for (var i = 0; i < consolidatedRanges.length; ++i) {
    +        var range = consolidatedRanges[i];
    +        out.push(encodeEscape(range[0]));
    +        if (range[1] > range[0]) {
    +          if (range[1] + 1 > range[0]) { out.push('-'); }
    +          out.push(encodeEscape(range[1]));
    +        }
    +      }
    +      out.push(']');
    +      return out.join('');
    +    }
    +
    +    function allowAnywhereFoldCaseAndRenumberGroups(regex) {
    +      // Split into character sets, escape sequences, punctuation strings
    +      // like ('(', '(?:', ')', '^'), and runs of characters that do not
    +      // include any of the above.
    +      var parts = regex.source.match(
    +          new RegExp(
    +              '(?:'
    +              + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
    +              + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
    +              + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
    +              + '|\\\\[0-9]+'  // a back-reference or octal escape
    +              + '|\\\\[^ux0-9]'  // other escape sequence
    +              + '|\\(\\?[:!=]'  // start of a non-capturing group
    +              + '|[\\(\\)\\^]'  // start/emd of a group, or line start
    +              + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
    +              + ')',
    +              'g'));
    +      var n = parts.length;
    +
    +      // Maps captured group numbers to the number they will occupy in
    +      // the output or to -1 if that has not been determined, or to
    +      // undefined if they need not be capturing in the output.
    +      var capturedGroups = [];
    +
    +      // Walk over and identify back references to build the capturedGroups
    +      // mapping.
    +      for (var i = 0, groupIndex = 0; i < n; ++i) {
    +        var p = parts[i];
    +        if (p === '(') {
    +          // groups are 1-indexed, so max group index is count of '('
    +          ++groupIndex;
    +        } else if ('\\' === p.charAt(0)) {
    +          var decimalValue = +p.substring(1);
    +          if (decimalValue && decimalValue <= groupIndex) {
    +            capturedGroups[decimalValue] = -1;
    +          }
    +        }
    +      }
    +
    +      // Renumber groups and reduce capturing groups to non-capturing groups
    +      // where possible.
    +      for (var i = 1; i < capturedGroups.length; ++i) {
    +        if (-1 === capturedGroups[i]) {
    +          capturedGroups[i] = ++capturedGroupIndex;
    +        }
    +      }
    +      for (var i = 0, groupIndex = 0; i < n; ++i) {
    +        var p = parts[i];
    +        if (p === '(') {
    +          ++groupIndex;
    +          if (capturedGroups[groupIndex] === undefined) {
    +            parts[i] = '(?:';
    +          }
    +        } else if ('\\' === p.charAt(0)) {
    +          var decimalValue = +p.substring(1);
    +          if (decimalValue && decimalValue <= groupIndex) {
    +            parts[i] = '\\' + capturedGroups[groupIndex];
    +          }
    +        }
    +      }
    +
    +      // Remove any prefix anchors so that the output will match anywhere.
    +      // ^^ really does mean an anchored match though.
    +      for (var i = 0, groupIndex = 0; i < n; ++i) {
    +        if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
    +      }
    +
    +      // Expand letters to groupts to handle mixing of case-sensitive and
    +      // case-insensitive patterns if necessary.
    +      if (regex.ignoreCase && needToFoldCase) {
    +        for (var i = 0; i < n; ++i) {
    +          var p = parts[i];
    +          var ch0 = p.charAt(0);
    +          if (p.length >= 2 && ch0 === '[') {
    +            parts[i] = caseFoldCharset(p);
    +          } else if (ch0 !== '\\') {
    +            // TODO: handle letters in numeric escapes.
    +            parts[i] = p.replace(
    +                /[a-zA-Z]/g,
    +                function (ch) {
    +                  var cc = ch.charCodeAt(0);
    +                  return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
    +                });
    +          }
    +        }
    +      }
    +
    +      return parts.join('');
    +    }
    +
    +    var rewritten = [];
    +    for (var i = 0, n = regexs.length; i < n; ++i) {
    +      var regex = regexs[i];
    +      if (regex.global || regex.multiline) { throw new Error('' + regex); }
    +      rewritten.push(
    +          '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
    +    }
    +
    +    return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
    +  }
    +
    +  var PR_innerHtmlWorks = null;
    +  function getInnerHtml(node) {
    +    // inner html is hopelessly broken in Safari 2.0.4 when the content is
    +    // an html description of well formed XML and the containing tag is a PRE
    +    // tag, so we detect that case and emulate innerHTML.
    +    if (null === PR_innerHtmlWorks) {
    +      var testNode = document.createElement('PRE');
    +      testNode.appendChild(
    +          document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));
    +      PR_innerHtmlWorks = !/</.test(testNode.innerHTML);
    +    }
    +
    +    if (PR_innerHtmlWorks) {
    +      var content = node.innerHTML;
    +      // XMP tags contain unescaped entities so require special handling.
    +      if (isRawContent(node)) {
    +        content = textToHtml(content);
    +      } else if (!isPreformatted(node, content)) {
    +        content = content.replace(/(<br\s*\/?>)[\r\n]+/g, '$1')
    +            .replace(/(?:[\r\n]+[ \t]*)+/g, ' ');
    +      }
    +      return content;
    +    }
    +
    +    var out = [];
    +    for (var child = node.firstChild; child; child = child.nextSibling) {
    +      normalizedHtml(child, out);
    +    }
    +    return out.join('');
    +  }
    +
    +  /** returns a function that expand tabs to spaces.  This function can be fed
    +    * successive chunks of text, and will maintain its own internal state to
    +    * keep track of how tabs are expanded.
    +    * @return {function (string) : string} a function that takes
    +    *   plain text and return the text with tabs expanded.
    +    * @private
    +    */
    +  function makeTabExpander(tabWidth) {
    +    var SPACES = '                ';
    +    var charInLine = 0;
    +
    +    return function (plainText) {
    +      // walk over each character looking for tabs and newlines.
    +      // On tabs, expand them.  On newlines, reset charInLine.
    +      // Otherwise increment charInLine
    +      var out = null;
    +      var pos = 0;
    +      for (var i = 0, n = plainText.length; i < n; ++i) {
    +        var ch = plainText.charAt(i);
    +
    +        switch (ch) {
    +          case '\t':
    +            if (!out) { out = []; }
    +            out.push(plainText.substring(pos, i));
    +            // calculate how much space we need in front of this part
    +            // nSpaces is the amount of padding -- the number of spaces needed
    +            // to move us to the next column, where columns occur at factors of
    +            // tabWidth.
    +            var nSpaces = tabWidth - (charInLine % tabWidth);
    +            charInLine += nSpaces;
    +            for (; nSpaces >= 0; nSpaces -= SPACES.length) {
    +              out.push(SPACES.substring(0, nSpaces));
    +            }
    +            pos = i + 1;
    +            break;
    +          case '\n':
    +            charInLine = 0;
    +            break;
    +          default:
    +            ++charInLine;
    +        }
    +      }
    +      if (!out) { return plainText; }
    +      out.push(plainText.substring(pos));
    +      return out.join('');
    +    };
    +  }
    +
    +  var pr_chunkPattern = new RegExp(
    +      '[^<]+'  // A run of characters other than '<'
    +      + '|<\!--[\\s\\S]*?--\>'  // an HTML comment
    +      + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>'  // a CDATA section
    +      // a probable tag that should not be highlighted
    +      + '|<\/?[a-zA-Z](?:[^>\"\']|\'[^\']*\'|\"[^\"]*\")*>'
    +      + '|<',  // A '<' that does not begin a larger chunk
    +      'g');
    +  var pr_commentPrefix = /^<\!--/;
    +  var pr_cdataPrefix = /^<!\[CDATA\[/;
    +  var pr_brPrefix = /^<br\b/i;
    +  var pr_tagNameRe = /^<(\/?)([a-zA-Z][a-zA-Z0-9]*)/;
    +
    +  /** split markup into chunks of html tags (style null) and
    +    * plain text (style {@link #PR_PLAIN}), converting tags which are
    +    * significant for tokenization (<br>) into their textual equivalent.
    +    *
    +    * @param {string} s html where whitespace is considered significant.
    +    * @return {Object} source code and extracted tags.
    +    * @private
    +    */
    +  function extractTags(s) {
    +    // since the pattern has the 'g' modifier and defines no capturing groups,
    +    // this will return a list of all chunks which we then classify and wrap as
    +    // PR_Tokens
    +    var matches = s.match(pr_chunkPattern);
    +    var sourceBuf = [];
    +    var sourceBufLen = 0;
    +    var extractedTags = [];
    +    if (matches) {
    +      for (var i = 0, n = matches.length; i < n; ++i) {
    +        var match = matches[i];
    +        if (match.length > 1 && match.charAt(0) === '<') {
    +          if (pr_commentPrefix.test(match)) { continue; }
    +          if (pr_cdataPrefix.test(match)) {
    +            // strip CDATA prefix and suffix.  Don't unescape since it's CDATA
    +            sourceBuf.push(match.substring(9, match.length - 3));
    +            sourceBufLen += match.length - 12;
    +          } else if (pr_brPrefix.test(match)) {
    +            // <br> tags are lexically significant so convert them to text.
    +            // This is undone later.
    +            sourceBuf.push('\n');
    +            ++sourceBufLen;
    +          } else {
    +            if (match.indexOf(PR_NOCODE) >= 0 && isNoCodeTag(match)) {
    +              // A <span class="nocode"> will start a section that should be
    +              // ignored.  Continue walking the list until we see a matching end
    +              // tag.
    +              var name = match.match(pr_tagNameRe)[2];
    +              var depth = 1;
    +              var j;
    +              end_tag_loop:
    +              for (j = i + 1; j < n; ++j) {
    +                var name2 = matches[j].match(pr_tagNameRe);
    +                if (name2 && name2[2] === name) {
    +                  if (name2[1] === '/') {
    +                    if (--depth === 0) { break end_tag_loop; }
    +                  } else {
    +                    ++depth;
    +                  }
    +                }
    +              }
    +              if (j < n) {
    +                extractedTags.push(
    +                    sourceBufLen, matches.slice(i, j + 1).join(''));
    +                i = j;
    +              } else {  // Ignore unclosed sections.
    +                extractedTags.push(sourceBufLen, match);
    +              }
    +            } else {
    +              extractedTags.push(sourceBufLen, match);
    +            }
    +          }
    +        } else {
    +          var literalText = htmlToText(match);
    +          sourceBuf.push(literalText);
    +          sourceBufLen += literalText.length;
    +        }
    +      }
    +    }
    +    return { source: sourceBuf.join(''), tags: extractedTags };
    +  }
    +
    +  /** True if the given tag contains a class attribute with the nocode class. */
    +  function isNoCodeTag(tag) {
    +    return !!tag
    +        // First canonicalize the representation of attributes
    +        .replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,
    +                 ' $1="$2$3$4"')
    +        // Then look for the attribute we want.
    +        .match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/);
    +  }
    +
    +  /**
    +   * Apply the given language handler to sourceCode and add the resulting
    +   * decorations to out.
    +   * @param {number} basePos the index of sourceCode within the chunk of source
    +   *    whose decorations are already present on out.
    +   */
    +  function appendDecorations(basePos, sourceCode, langHandler, out) {
    +    if (!sourceCode) { return; }
    +    var job = {
    +      source: sourceCode,
    +      basePos: basePos
    +    };
    +    langHandler(job);
    +    out.push.apply(out, job.decorations);
    +  }
    +
    +  /** Given triples of [style, pattern, context] returns a lexing function,
    +    * The lexing function interprets the patterns to find token boundaries and
    +    * returns a decoration list of the form
    +    * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
    +    * where index_n is an index into the sourceCode, and style_n is a style
    +    * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
    +    * all characters in sourceCode[index_n-1:index_n].
    +    *
    +    * The stylePatterns is a list whose elements have the form
    +    * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
    +    *
    +    * Style is a style constant like PR_PLAIN, or can be a string of the
    +    * form 'lang-FOO', where FOO is a language extension describing the
    +    * language of the portion of the token in $1 after pattern executes.
    +    * E.g., if style is 'lang-lisp', and group 1 contains the text
    +    * '(hello (world))', then that portion of the token will be passed to the
    +    * registered lisp handler for formatting.
    +    * The text before and after group 1 will be restyled using this decorator
    +    * so decorators should take care that this doesn't result in infinite
    +    * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
    +    * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
    +    * '<script>foo()<\/script>', which would cause the current decorator to
    +    * be called with '<script>' which would not match the same rule since
    +    * group 1 must not be empty, so it would be instead styled as PR_TAG by
    +    * the generic tag rule.  The handler registered for the 'js' extension would
    +    * then be called with 'foo()', and finally, the current decorator would
    +    * be called with '<\/script>' which would not match the original rule and
    +    * so the generic tag rule would identify it as a tag.
    +    *
    +    * Pattern must only match prefixes, and if it matches a prefix, then that
    +    * match is considered a token with the same style.
    +    *
    +    * Context is applied to the last non-whitespace, non-comment token
    +    * recognized.
    +    *
    +    * Shortcut is an optional string of characters, any of which, if the first
    +    * character, gurantee that this pattern and only this pattern matches.
    +    *
    +    * @param {Array} shortcutStylePatterns patterns that always start with
    +    *   a known character.  Must have a shortcut string.
    +    * @param {Array} fallthroughStylePatterns patterns that will be tried in
    +    *   order if the shortcut ones fail.  May have shortcuts.
    +    *
    +    * @return {function (Object)} a
    +    *   function that takes source code and returns a list of decorations.
    +    */
    +  function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
    +    var shortcuts = {};
    +    var tokenizer;
    +    (function () {
    +      var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
    +      var allRegexs = [];
    +      var regexKeys = {};
    +      for (var i = 0, n = allPatterns.length; i < n; ++i) {
    +        var patternParts = allPatterns[i];
    +        var shortcutChars = patternParts[3];
    +        if (shortcutChars) {
    +          for (var c = shortcutChars.length; --c >= 0;) {
    +            shortcuts[shortcutChars.charAt(c)] = patternParts;
    +          }
    +        }
    +        var regex = patternParts[1];
    +        var k = '' + regex;
    +        if (!regexKeys.hasOwnProperty(k)) {
    +          allRegexs.push(regex);
    +          regexKeys[k] = null;
    +        }
    +      }
    +      allRegexs.push(/[\0-\uffff]/);
    +      tokenizer = combinePrefixPatterns(allRegexs);
    +    })();
    +
    +    var nPatterns = fallthroughStylePatterns.length;
    +    var notWs = /\S/;
    +
    +    /**
    +     * Lexes job.source and produces an output array job.decorations of style
    +     * classes preceded by the position at which they start in job.source in
    +     * order.
    +     *
    +     * @param {Object} job an object like {@code
    +     *    source: {string} sourceText plain text,
    +     *    basePos: {int} position of job.source in the larger chunk of
    +     *        sourceCode.
    +     * }
    +     */
    +    var decorate = function (job) {
    +      var sourceCode = job.source, basePos = job.basePos;
    +      /** Even entries are positions in source in ascending order.  Odd enties
    +        * are style markers (e.g., PR_COMMENT) that run from that position until
    +        * the end.
    +        * @type {Array.<number|string>}
    +        */
    +      var decorations = [basePos, PR_PLAIN];
    +      var pos = 0;  // index into sourceCode
    +      var tokens = sourceCode.match(tokenizer) || [];
    +      var styleCache = {};
    +
    +      for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
    +        var token = tokens[ti];
    +        var style = styleCache[token];
    +        var match = void 0;
    +
    +        var isEmbedded;
    +        if (typeof style === 'string') {
    +          isEmbedded = false;
    +        } else {
    +          var patternParts = shortcuts[token.charAt(0)];
    +          if (patternParts) {
    +            match = token.match(patternParts[1]);
    +            style = patternParts[0];
    +          } else {
    +            for (var i = 0; i < nPatterns; ++i) {
    +              patternParts = fallthroughStylePatterns[i];
    +              match = token.match(patternParts[1]);
    +              if (match) {
    +                style = patternParts[0];
    +                break;
    +              }
    +            }
    +
    +            if (!match) {  // make sure that we make progress
    +              style = PR_PLAIN;
    +            }
    +          }
    +
    +          isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
    +          if (isEmbedded && !(match && typeof match[1] === 'string')) {
    +            isEmbedded = false;
    +            style = PR_SOURCE;
    +          }
    +
    +          if (!isEmbedded) { styleCache[token] = style; }
    +        }
    +
    +        var tokenStart = pos;
    +        pos += token.length;
    +
    +        if (!isEmbedded) {
    +          decorations.push(basePos + tokenStart, style);
    +        } else {  // Treat group 1 as an embedded block of source code.
    +          var embeddedSource = match[1];
    +          var embeddedSourceStart = token.indexOf(embeddedSource);
    +          var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
    +          if (match[2]) {
    +            // If embeddedSource can be blank, then it would match at the
    +            // beginning which would cause us to infinitely recurse on the
    +            // entire token, so we catch the right context in match[2].
    +            embeddedSourceEnd = token.length - match[2].length;
    +            embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
    +          }
    +          var lang = style.substring(5);
    +          // Decorate the left of the embedded source
    +          appendDecorations(
    +              basePos + tokenStart,
    +              token.substring(0, embeddedSourceStart),
    +              decorate, decorations);
    +          // Decorate the embedded source
    +          appendDecorations(
    +              basePos + tokenStart + embeddedSourceStart,
    +              embeddedSource,
    +              langHandlerForExtension(lang, embeddedSource),
    +              decorations);
    +          // Decorate the right of the embedded section
    +          appendDecorations(
    +              basePos + tokenStart + embeddedSourceEnd,
    +              token.substring(embeddedSourceEnd),
    +              decorate, decorations);
    +        }
    +      }
    +      job.decorations = decorations;
    +    };
    +    return decorate;
    +  }
    +
    +  /** returns a function that produces a list of decorations from source text.
    +    *
    +    * This code treats ", ', and ` as string delimiters, and \ as a string
    +    * escape.  It does not recognize perl's qq() style strings.
    +    * It has no special handling for double delimiter escapes as in basic, or
    +    * the tripled delimiters used in python, but should work on those regardless
    +    * although in those cases a single string literal may be broken up into
    +    * multiple adjacent string literals.
    +    *
    +    * It recognizes C, C++, and shell style comments.
    +    *
    +    * @param {Object} options a set of optional parameters.
    +    * @return {function (Object)} a function that examines the source code
    +    *     in the input job and builds the decoration list.
    +    */
    +  function sourceDecorator(options) {
    +    var shortcutStylePatterns = [], fallthroughStylePatterns = [];
    +    if (options['tripleQuotedStrings']) {
    +      // '''multi-line-string''', 'single-line-string', and double-quoted
    +      shortcutStylePatterns.push(
    +          [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
    +           null, '\'"']);
    +    } else if (options['multiLineStrings']) {
    +      // 'multi-line-string', "multi-line-string"
    +      shortcutStylePatterns.push(
    +          [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
    +           null, '\'"`']);
    +    } else {
    +      // 'single-line-string', "single-line-string"
    +      shortcutStylePatterns.push(
    +          [PR_STRING,
    +           /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
    +           null, '"\'']);
    +    }
    +    if (options['verbatimStrings']) {
    +      // verbatim-string-literal production from the C# grammar.  See issue 93.
    +      fallthroughStylePatterns.push(
    +          [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
    +    }
    +    if (options['hashComments']) {
    +      if (options['cStyleComments']) {
    +        // Stop C preprocessor declarations at an unclosed open comment
    +        shortcutStylePatterns.push(
    +            [PR_COMMENT, /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,
    +             null, '#']);
    +        fallthroughStylePatterns.push(
    +            [PR_STRING,
    +             /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
    +             null]);
    +      } else {
    +        shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
    +      }
    +    }
    +    if (options['cStyleComments']) {
    +      fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
    +      fallthroughStylePatterns.push(
    +          [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
    +    }
    +    if (options['regexLiterals']) {
    +      var REGEX_LITERAL = (
    +          // A regular expression literal starts with a slash that is
    +          // not followed by * or / so that it is not confused with
    +          // comments.
    +          '/(?=[^/*])'
    +          // and then contains any number of raw characters,
    +          + '(?:[^/\\x5B\\x5C]'
    +          // escape sequences (\x5C),
    +          +    '|\\x5C[\\s\\S]'
    +          // or non-nesting character sets (\x5B\x5D);
    +          +    '|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+'
    +          // finally closed by a /.
    +          + '/');
    +      fallthroughStylePatterns.push(
    +          ['lang-regex',
    +           new RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
    +           ]);
    +    }
    +
    +    var keywords = options['keywords'].replace(/^\s+|\s+$/g, '');
    +    if (keywords.length) {
    +      fallthroughStylePatterns.push(
    +          [PR_KEYWORD,
    +           new RegExp('^(?:' + keywords.replace(/\s+/g, '|') + ')\\b'), null]);
    +    }
    +
    +    shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
    +    fallthroughStylePatterns.push(
    +        // TODO(mikesamuel): recognize non-latin letters and numerals in idents
    +        [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
    +        [PR_TYPE,        /^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/, null],
    +        [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
    +        [PR_LITERAL,
    +         new RegExp(
    +             '^(?:'
    +             // A hex number
    +             + '0x[a-f0-9]+'
    +             // or an octal or decimal number,
    +             + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
    +             // possibly in scientific notation
    +             + '(?:e[+\\-]?\\d+)?'
    +             + ')'
    +             // with an optional modifier like UL for unsigned long
    +             + '[a-z]*', 'i'),
    +         null, '0123456789'],
    +        [PR_PUNCTUATION, /^.[^\s\w\.$@\'\"\`\/\#]*/, null]);
    +
    +    return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
    +  }
    +
    +  var decorateSource = sourceDecorator({
    +        'keywords': ALL_KEYWORDS,
    +        'hashComments': true,
    +        'cStyleComments': true,
    +        'multiLineStrings': true,
    +        'regexLiterals': true
    +      });
    +
    +  /** Breaks {@code job.source} around style boundaries in
    +    * {@code job.decorations} while re-interleaving {@code job.extractedTags},
    +    * and leaves the result in {@code job.prettyPrintedHtml}.
    +    * @param {Object} job like {
    +    *    source: {string} source as plain text,
    +    *    extractedTags: {Array.<number|string>} extractedTags chunks of raw
    +    *                   html preceded by their position in {@code job.source}
    +    *                   in order
    +    *    decorations: {Array.<number|string} an array of style classes preceded
    +    *                 by the position at which they start in job.source in order
    +    * }
    +    * @private
    +    */
    +  function recombineTagsAndDecorations(job) {
    +    var sourceText = job.source;
    +    var extractedTags = job.extractedTags;
    +    var decorations = job.decorations;
    +
    +    var html = [];
    +    // index past the last char in sourceText written to html
    +    var outputIdx = 0;
    +
    +    var openDecoration = null;
    +    var currentDecoration = null;
    +    var tagPos = 0;  // index into extractedTags
    +    var decPos = 0;  // index into decorations
    +    var tabExpander = makeTabExpander(window['PR_TAB_WIDTH']);
    +
    +    var adjacentSpaceRe = /([\r\n ]) /g;
    +    var startOrSpaceRe = /(^| ) /gm;
    +    var newlineRe = /\r\n?|\n/g;
    +    var trailingSpaceRe = /[ \r\n]$/;
    +    var lastWasSpace = true;  // the last text chunk emitted ended with a space.
    +
    +    // See bug 71 and http://stackoverflow.com/questions/136443/why-doesnt-ie7-
    +    var isIE678 = window['_pr_isIE6']();
    +    var lineBreakHtml = (
    +        isIE678
    +        ? (job.sourceNode.tagName === 'PRE'
    +           // Use line feeds instead of <br>s so that copying and pasting works
    +           // on IE.
    +           // Doing this on other browsers breaks lots of stuff since \r\n is
    +           // treated as two newlines on Firefox.
    +           ? (isIE678 === 6 ? '&#160;\r\n' :
    +              isIE678 === 7 ? '&#160;<br>\r' : '&#160;\r')
    +           // IE collapses multiple adjacent <br>s into 1 line break.
    +           // Prefix every newline with '&#160;' to prevent such behavior.
    +           // &nbsp; is the same as &#160; but works in XML as well as HTML.
    +           : '&#160;<br />')
    +        : '<br />');
    +
    +    // Look for a class like linenums or linenums:<n> where <n> is the 1-indexed
    +    // number of the first line.
    +    var numberLines = job.sourceNode.className.match(/\blinenums\b(?::(\d+))?/);
    +    var lineBreaker;
    +    if (numberLines) {
    +      var lineBreaks = [];
    +      for (var i = 0; i < 10; ++i) {
    +        lineBreaks[i] = lineBreakHtml + '</li><li class="L' + i + '">';
    +      }
    +      var lineNum = numberLines[1] && numberLines[1].length
    +          ? numberLines[1] - 1 : 0;  // Lines are 1-indexed
    +      html.push('<ol class="linenums"><li class="L', (lineNum) % 10, '"');
    +      if (lineNum) {
    +        html.push(' value="', lineNum + 1, '"');
    +      }
    +      html.push('>');
    +      lineBreaker = function () {
    +        var lb = lineBreaks[++lineNum % 10];
    +        // If a decoration is open, we need to close it before closing a list-item
    +        // and reopen it on the other side of the list item.
    +        return openDecoration
    +            ? ('</span>' + lb + '<span class="' + openDecoration + '">') : lb;
    +      };
    +    } else {
    +      lineBreaker = lineBreakHtml;
    +    }
    +
    +    // A helper function that is responsible for opening sections of decoration
    +    // and outputing properly escaped chunks of source
    +    function emitTextUpTo(sourceIdx) {
    +      if (sourceIdx > outputIdx) {
    +        if (openDecoration && openDecoration !== currentDecoration) {
    +          // Close the current decoration
    +          html.push('</span>');
    +          openDecoration = null;
    +        }
    +        if (!openDecoration && currentDecoration) {
    +          openDecoration = currentDecoration;
    +          html.push('<span class="', openDecoration, '">');
    +        }
    +        // This interacts badly with some wikis which introduces paragraph tags
    +        // into pre blocks for some strange reason.
    +        // It's necessary for IE though which seems to lose the preformattedness
    +        // of <pre> tags when their innerHTML is assigned.
    +        // http://stud3.tuwien.ac.at/~e0226430/innerHtmlQuirk.html
    +        // and it serves to undo the conversion of <br>s to newlines done in
    +        // chunkify.
    +        var htmlChunk = textToHtml(
    +            tabExpander(sourceText.substring(outputIdx, sourceIdx)))
    +            .replace(lastWasSpace
    +                     ? startOrSpaceRe
    +                     : adjacentSpaceRe, '$1&#160;');
    +        // Keep track of whether we need to escape space at the beginning of the
    +        // next chunk.
    +        lastWasSpace = trailingSpaceRe.test(htmlChunk);
    +        html.push(htmlChunk.replace(newlineRe, lineBreaker));
    +        outputIdx = sourceIdx;
    +      }
    +    }
    +
    +    while (true) {
    +      // Determine if we're going to consume a tag this time around.  Otherwise
    +      // we consume a decoration or exit.
    +      var outputTag;
    +      if (tagPos < extractedTags.length) {
    +        if (decPos < decorations.length) {
    +          // Pick one giving preference to extractedTags since we shouldn't open
    +          // a new style that we're going to have to immediately close in order
    +          // to output a tag.
    +          outputTag = extractedTags[tagPos] <= decorations[decPos];
    +        } else {
    +          outputTag = true;
    +        }
    +      } else {
    +        outputTag = false;
    +      }
    +      // Consume either a decoration or a tag or exit.
    +      if (outputTag) {
    +        emitTextUpTo(extractedTags[tagPos]);
    +        if (openDecoration) {
    +          // Close the current decoration
    +          html.push('</span>');
    +          openDecoration = null;
    +        }
    +        html.push(extractedTags[tagPos + 1]);
    +        tagPos += 2;
    +      } else if (decPos < decorations.length) {
    +        emitTextUpTo(decorations[decPos]);
    +        currentDecoration = decorations[decPos + 1];
    +        decPos += 2;
    +      } else {
    +        break;
    +      }
    +    }
    +    emitTextUpTo(sourceText.length);
    +    if (openDecoration) {
    +      html.push('</span>');
    +    }
    +    if (numberLines) { html.push('</li></ol>'); }
    +    job.prettyPrintedHtml = html.join('');
    +  }
    +
    +  /** Maps language-specific file extensions to handlers. */
    +  var langHandlerRegistry = {};
    +  /** Register a language handler for the given file extensions.
    +    * @param {function (Object)} handler a function from source code to a list
    +    *      of decorations.  Takes a single argument job which describes the
    +    *      state of the computation.   The single parameter has the form
    +    *      {@code {
    +    *        source: {string} as plain text.
    +    *        decorations: {Array.<number|string>} an array of style classes
    +    *                     preceded by the position at which they start in
    +    *                     job.source in order.
    +    *                     The language handler should assigned this field.
    +    *        basePos: {int} the position of source in the larger source chunk.
    +    *                 All positions in the output decorations array are relative
    +    *                 to the larger source chunk.
    +    *      } }
    +    * @param {Array.<string>} fileExtensions
    +    */
    +  function registerLangHandler(handler, fileExtensions) {
    +    for (var i = fileExtensions.length; --i >= 0;) {
    +      var ext = fileExtensions[i];
    +      if (!langHandlerRegistry.hasOwnProperty(ext)) {
    +        langHandlerRegistry[ext] = handler;
    +      } else if ('console' in window) {
    +        console['warn']('cannot override language handler %s', ext);
    +      }
    +    }
    +  }
    +  function langHandlerForExtension(extension, source) {
    +    if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
    +      // Treat it as markup if the first non whitespace character is a < and
    +      // the last non-whitespace character is a >.
    +      extension = /^\s*</.test(source)
    +          ? 'default-markup'
    +          : 'default-code';
    +    }
    +    return langHandlerRegistry[extension];
    +  }
    +  registerLangHandler(decorateSource, ['default-code']);
    +  registerLangHandler(
    +      createSimpleLexer(
    +          [],
    +          [
    +           [PR_PLAIN,       /^[^<?]+/],
    +           [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
    +           [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
    +           // Unescaped content in an unknown language
    +           ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
    +           ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
    +           [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
    +           ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
    +           // Unescaped content in javascript.  (Or possibly vbscript).
    +           ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
    +           // Contains unescaped stylesheet content
    +           ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
    +           ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
    +          ]),
    +      ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
    +  registerLangHandler(
    +      createSimpleLexer(
    +          [
    +           [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
    +           [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
    +           ],
    +          [
    +           [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
    +           [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
    +           ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
    +           [PR_PUNCTUATION,  /^[=<>\/]+/],
    +           ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
    +           ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
    +           ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
    +           ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
    +           ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
    +           ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
    +           ]),
    +      ['in.tag']);
    +  registerLangHandler(
    +      createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': CPP_KEYWORDS,
    +          'hashComments': true,
    +          'cStyleComments': true
    +        }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': 'null true false'
    +        }), ['json']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': CSHARP_KEYWORDS,
    +          'hashComments': true,
    +          'cStyleComments': true,
    +          'verbatimStrings': true
    +        }), ['cs']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': JAVA_KEYWORDS,
    +          'cStyleComments': true
    +        }), ['java']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': SH_KEYWORDS,
    +          'hashComments': true,
    +          'multiLineStrings': true
    +        }), ['bsh', 'csh', 'sh']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': PYTHON_KEYWORDS,
    +          'hashComments': true,
    +          'multiLineStrings': true,
    +          'tripleQuotedStrings': true
    +        }), ['cv', 'py']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': PERL_KEYWORDS,
    +          'hashComments': true,
    +          'multiLineStrings': true,
    +          'regexLiterals': true
    +        }), ['perl', 'pl', 'pm']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': RUBY_KEYWORDS,
    +          'hashComments': true,
    +          'multiLineStrings': true,
    +          'regexLiterals': true
    +        }), ['rb']);
    +  registerLangHandler(sourceDecorator({
    +          'keywords': JSCRIPT_KEYWORDS,
    +          'cStyleComments': true,
    +          'regexLiterals': true
    +        }), ['js']);
    +  registerLangHandler(
    +      createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
    +
    +  function applyDecorator(job) {
    +    var sourceCodeHtml = job.sourceCodeHtml;
    +    var opt_langExtension = job.langExtension;
    +
    +    // Prepopulate output in case processing fails with an exception.
    +    job.prettyPrintedHtml = sourceCodeHtml;
    +
    +    try {
    +      // Extract tags, and convert the source code to plain text.
    +      var sourceAndExtractedTags = extractTags(sourceCodeHtml);
    +      /** Plain text. @type {string} */
    +      var source = sourceAndExtractedTags.source;
    +      job.source = source;
    +      job.basePos = 0;
    +
    +      /** Even entries are positions in source in ascending order.  Odd entries
    +        * are tags that were extracted at that position.
    +        * @type {Array.<number|string>}
    +        */
    +      job.extractedTags = sourceAndExtractedTags.tags;
    +
    +      // Apply the appropriate language handler
    +      langHandlerForExtension(opt_langExtension, source)(job);
    +      // Integrate the decorations and tags back into the source code to produce
    +      // a decorated html string which is left in job.prettyPrintedHtml.
    +      recombineTagsAndDecorations(job);
    +    } catch (e) {
    +      if ('console' in window) {
    +        console['log'](e && e['stack'] ? e['stack'] : e);
    +      }
    +    }
    +  }
    +
    +  function prettyPrintOne(sourceCodeHtml, opt_langExtension) {
    +    var job = {
    +      sourceCodeHtml: sourceCodeHtml,
    +      langExtension: opt_langExtension
    +    };
    +    applyDecorator(job);
    +    return job.prettyPrintedHtml;
    +  }
    +
    +  function prettyPrint(opt_whenDone) {
    +    function byTagName(tn) { return document.getElementsByTagName(tn); }
    +    // fetch a list of nodes to rewrite
    +    var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
    +    var elements = [];
    +    for (var i = 0; i < codeSegments.length; ++i) {
    +      for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
    +        elements.push(codeSegments[i][j]);
    +      }
    +    }
    +    codeSegments = null;
    +
    +    var clock = Date;
    +    if (!clock['now']) {
    +      clock = { 'now': function () { return (new Date).getTime(); } };
    +    }
    +
    +    // The loop is broken into a series of continuations to make sure that we
    +    // don't make the browser unresponsive when rewriting a large page.
    +    var k = 0;
    +    var prettyPrintingJob;
    +
    +    function doWork() {
    +      var endTime = (window['PR_SHOULD_USE_CONTINUATION'] ?
    +                     clock.now() + 250 /* ms */ :
    +                     Infinity);
    +      for (; k < elements.length && clock.now() < endTime; k++) {
    +        var cs = elements[k];
    +        // [JACOCO] 'prettyprint' -> 'source'
    +        if (cs.className && cs.className.indexOf('source') >= 0) {
    +          // If the classes includes a language extensions, use it.
    +          // Language extensions can be specified like
    +          //     <pre class="prettyprint lang-cpp">
    +          // the language extension "cpp" is used to find a language handler as
    +          // passed to PR_registerLangHandler.
    +          var langExtension = cs.className.match(/\blang-(\w+)\b/);
    +          if (langExtension) { langExtension = langExtension[1]; }
    +
    +          // make sure this is not nested in an already prettified element
    +          var nested = false;
    +          for (var p = cs.parentNode; p; p = p.parentNode) {
    +            if ((p.tagName === 'pre' || p.tagName === 'code' ||
    +                 p.tagName === 'xmp') &&
    +                // [JACOCO] 'prettyprint' -> 'source'
    +                p.className && p.className.indexOf('source') >= 0) {
    +              nested = true;
    +              break;
    +            }
    +          }
    +          if (!nested) {
    +            // fetch the content as a snippet of properly escaped HTML.
    +            // Firefox adds newlines at the end.
    +            var content = getInnerHtml(cs);
    +            content = content.replace(/(?:\r\n?|\n)$/, '');
    +
    +            // do the pretty printing
    +            prettyPrintingJob = {
    +              sourceCodeHtml: content,
    +              langExtension: langExtension,
    +              sourceNode: cs
    +            };
    +            applyDecorator(prettyPrintingJob);
    +            replaceWithPrettyPrintedHtml();
    +          }
    +        }
    +      }
    +      if (k < elements.length) {
    +        // finish up in a continuation
    +        setTimeout(doWork, 250);
    +      } else if (opt_whenDone) {
    +        opt_whenDone();
    +      }
    +    }
    +
    +    function replaceWithPrettyPrintedHtml() {
    +      var newContent = prettyPrintingJob.prettyPrintedHtml;
    +      if (!newContent) { return; }
    +      var cs = prettyPrintingJob.sourceNode;
    +
    +      // push the prettified html back into the tag.
    +      if (!isRawContent(cs)) {
    +        // just replace the old html with the new
    +        cs.innerHTML = newContent;
    +      } else {
    +        // we need to change the tag to a <pre> since <xmp>s do not allow
    +        // embedded tags such as the span tags used to attach styles to
    +        // sections of source code.
    +        var pre = document.createElement('PRE');
    +        for (var i = 0; i < cs.attributes.length; ++i) {
    +          var a = cs.attributes[i];
    +          if (a.specified) {
    +            var aname = a.name.toLowerCase();
    +            if (aname === 'class') {
    +              pre.className = a.value;  // For IE 6
    +            } else {
    +              pre.setAttribute(a.name, a.value);
    +            }
    +          }
    +        }
    +        pre.innerHTML = newContent;
    +
    +        // remove the old
    +        cs.parentNode.replaceChild(pre, cs);
    +        cs = pre;
    +      }
    +    }
    +
    +    doWork();
    +  }
    +
    +  window['PR_normalizedHtml'] = normalizedHtml;
    +  window['prettyPrintOne'] = prettyPrintOne;
    +  window['prettyPrint'] = prettyPrint;
    +  window['PR'] = {
    +        'combinePrefixPatterns': combinePrefixPatterns,
    +        'createSimpleLexer': createSimpleLexer,
    +        'registerLangHandler': registerLangHandler,
    +        'sourceDecorator': sourceDecorator,
    +        'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
    +        'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
    +        'PR_COMMENT': PR_COMMENT,
    +        'PR_DECLARATION': PR_DECLARATION,
    +        'PR_KEYWORD': PR_KEYWORD,
    +        'PR_LITERAL': PR_LITERAL,
    +        'PR_NOCODE': PR_NOCODE,
    +        'PR_PLAIN': PR_PLAIN,
    +        'PR_PUNCTUATION': PR_PUNCTUATION,
    +        'PR_SOURCE': PR_SOURCE,
    +        'PR_STRING': PR_STRING,
    +        'PR_TAG': PR_TAG,
    +        'PR_TYPE': PR_TYPE
    +      };
    +})();
    diff --git a/testResult/html/jacoco-resources/redbar.gif b/testResult/html/jacoco-resources/redbar.gif
    new file mode 100644
    index 0000000000000000000000000000000000000000..c2f71469ba995289439d86ea39b1b33edb03388c
    GIT binary patch
    literal 91
    zcmZ?wbhEHbWMtrCc+AD{pP&D~tn7aso&R25|6^nS*Vg{;>G{84!T)8;{;yfXu$BQ0
    fDgI<(<YM4w&|v@qkQodt90ol_LPjnP91PX~3&9+X
    
    literal 0
    HcmV?d00001
    
    diff --git a/testResult/html/jacoco-resources/report.css b/testResult/html/jacoco-resources/report.css
    new file mode 100644
    index 000000000..dd936bca5
    --- /dev/null
    +++ b/testResult/html/jacoco-resources/report.css
    @@ -0,0 +1,243 @@
    +body, td {
    +  font-family:sans-serif;
    +  font-size:10pt;
    +}
    +
    +h1 {
    +  font-weight:bold;
    +  font-size:18pt;
    +}
    +
    +.breadcrumb {
    +  border:#d6d3ce 1px solid;
    +  padding:2px 4px 2px 4px;
    +}
    +
    +.breadcrumb .info {
    +  float:right;
    +}
    +
    +.breadcrumb .info a {
    +  margin-left:8px;
    +}
    +
    +.el_report {
    +  padding-left:18px;
    +  background-image:url(report.gif);
    +  background-position:left center;
    +  background-repeat:no-repeat;
    +}
    +
    +.el_group {
    +  padding-left:18px;
    +  background-image:url(group.gif);
    +  background-position:left center;
    +  background-repeat:no-repeat;
    +}
    +
    +.el_bundle {
    +  padding-left:18px;
    +  background-image:url(bundle.gif);
    +  background-position:left center;
    +  background-repeat:no-repeat;
    +}
    +
    +.el_package {
    +  padding-left:18px;
    +  background-image:url(package.gif);
    +  background-position:left center;
    +  background-repeat:no-repeat;
    +}
    +
    +.el_class {
    +  padding-left:18px;
    +  background-image:url(class.gif);
    +  background-position:left center;
    +  background-repeat:no-repeat;
    +}
    +
    +.el_source {
    +  padding-left:18px;
    +  background-image:url(source.gif);
    +  background-position:left center;
    +  background-repeat:no-repeat;
    +}
    +
    +.el_method {
    +  padding-left:18px;
    +  background-image:url(method.gif);
    +  background-position:left center;
    +  background-repeat:no-repeat;
    +}
    +
    +.el_session {
    +  padding-left:18px;
    +  background-image:url(session.gif);
    +  background-position:left center;
    +  background-repeat:no-repeat;
    +}
    +
    +pre.source {
    +  border:#d6d3ce 1px solid;
    +  font-family:monospace;
    +}
    +
    +pre.source ol {
    +  margin-bottom: 0px;
    +  margin-top: 0px;
    +}
    +
    +pre.source li {
    +  border-left: 1px solid #D6D3CE;
    +  color: #A0A0A0;
    +  padding-left: 0px;
    +}
    +
    +pre.source span.fc {
    +  background-color:#ccffcc;
    +}
    +
    +pre.source span.nc {
    +  background-color:#ffaaaa;
    +}
    +
    +pre.source span.pc {
    +  background-color:#ffffcc;
    +}
    +
    +pre.source span.bfc {
    +  background-image: url(branchfc.gif);
    +  background-repeat: no-repeat;
    +  background-position: 2px center;
    +}
    +
    +pre.source span.bfc:hover {
    +  background-color:#80ff80;
    +}
    +
    +pre.source span.bnc {
    +  background-image: url(branchnc.gif);
    +  background-repeat: no-repeat;
    +  background-position: 2px center;
    +}
    +
    +pre.source span.bnc:hover {
    +  background-color:#ff8080;
    +}
    +
    +pre.source span.bpc {
    +  background-image: url(branchpc.gif);
    +  background-repeat: no-repeat;
    +  background-position: 2px center;
    +}
    +
    +pre.source span.bpc:hover {
    +  background-color:#ffff80;
    +}
    +
    +table.coverage {
    +  empty-cells:show;
    +  border-collapse:collapse;
    +}
    +
    +table.coverage thead {
    +  background-color:#e0e0e0;
    +}
    +
    +table.coverage thead td {
    +  white-space:nowrap;
    +  padding:2px 14px 0px 6px;
    +  border-bottom:#b0b0b0 1px solid;
    +}
    +
    +table.coverage thead td.bar {
    +  border-left:#cccccc 1px solid;
    +}
    +
    +table.coverage thead td.ctr1 {
    +  text-align:right;
    +  border-left:#cccccc 1px solid;
    +}
    +
    +table.coverage thead td.ctr2 {
    +  text-align:right;
    +  padding-left:2px;
    +}
    +
    +table.coverage thead td.sortable {
    +  cursor:pointer;
    +  background-image:url(sort.gif);
    +  background-position:right center;
    +  background-repeat:no-repeat;
    +}
    +
    +table.coverage thead td.up {
    +  background-image:url(up.gif);
    +}
    +
    +table.coverage thead td.down {
    +  background-image:url(down.gif);
    +}
    +
    +table.coverage tbody td {
    +  white-space:nowrap;
    +  padding:2px 6px 2px 6px;
    +  border-bottom:#d6d3ce 1px solid;
    +}
    +
    +table.coverage tbody tr:hover {
    +  background: #f0f0d0 !important;
    +}
    +
    +table.coverage tbody td.bar {
    +  border-left:#e8e8e8 1px solid;
    +}
    +
    +table.coverage tbody td.ctr1 {
    +  text-align:right;
    +  padding-right:14px;
    +  border-left:#e8e8e8 1px solid;
    +}
    +
    +table.coverage tbody td.ctr2 {
    +  text-align:right;
    +  padding-right:14px;
    +  padding-left:2px;
    +}
    +
    +table.coverage tfoot td {
    +  white-space:nowrap;
    +  padding:2px 6px 2px 6px;
    +}
    +
    +table.coverage tfoot td.bar {
    +  border-left:#e8e8e8 1px solid;
    +}
    +
    +table.coverage tfoot td.ctr1 {
    +  text-align:right;
    +  padding-right:14px;
    +  border-left:#e8e8e8 1px solid;
    +}
    +
    +table.coverage tfoot td.ctr2 {
    +  text-align:right;
    +  padding-right:14px;
    +  padding-left:2px;
    +}
    +
    +.footer {
    +  margin-top:20px;
    +  border-top:#d6d3ce 1px solid;
    +  padding-top:2px;
    +  font-size:8pt;
    +  color:#a0a0a0;
    +}
    +
    +.footer a {
    +  color:#a0a0a0;
    +}
    +
    +.right {
    +  float:right;
    +}
    diff --git a/testResult/html/jacoco-resources/report.gif b/testResult/html/jacoco-resources/report.gif
    new file mode 100644
    index 0000000000000000000000000000000000000000..8547be50bf3e97e725920927b5aa4cdb031f4823
    GIT binary patch
    literal 363
    zcmZ?wbhEHb6krfwSZc{In}J~s1H&!`1_uX+xVSjMb&S>db~X8S)dhAn1$OlXwvB~0
    zO@%hC#Wq5_7&^+V`^qgRRa;E2HJ?*&DsqWoev|2fCetO&CQDmPR<;_iXfs~ZZnVC`
    za8s8-+pK*(^AAm4c5K#~(^ocST-lU)byMc8y)_R`^xu2&{oaco_g{R!|Ki8Pmp>lA
    z{_*VHkC*R%zWMa)!{^_hzyAL8?f2(zzrTL}{q@K1Z$Ey2|M}<VuRs5>0mYvzj9d)%
    z3_1)z0P+(9TgQR<1s*zF)+bahX*_u_??Pbv&V#KE^V2&`bhGjjR;*MxC8EFO_3_}<
    zH?w9WrJ7AX`tJM8r525X{~8+WorLsRL^?W{nR=L*odosT`KItOGtTI963}JgV_m??
    z%&>&9-=1G*^3>@wm-A|~FmK+nbvd`DhNhP0UUhXIS1vYAPL5-o?Ce}VXI&i`tO1G(
    BvdRDe
    
    literal 0
    HcmV?d00001
    
    diff --git a/testResult/html/jacoco-resources/session.gif b/testResult/html/jacoco-resources/session.gif
    new file mode 100644
    index 0000000000000000000000000000000000000000..0151bad8a001e5cc5cc7723a608185f746b7f8c1
    GIT binary patch
    literal 213
    zcmZ?wbhEHb6krfwXc1xPS$gU4xw~t2pG#?5#^Be>V3WrXI-S9<hrzA(|Nr^_@5k?-
    zZ~y=IhyVNSXZ04}pKqV%t9oe5k~tY+Ar=Pzi2#Z}Sr{1@<Qa4rfB<AC18dL&^}dwM
    zX_r*ys<8N;e6mS?i^dP8jVmAd@U^}&$uv>xc~m$hYN?d{@xrG~CzZCfhpBIRC}Q>I
    kiQ?_Ai=3VZEOFW9fBwaksdwMK(Err)E%VcVRYeAC06w^MK>z>%
    
    literal 0
    HcmV?d00001
    
    diff --git a/testResult/html/jacoco-resources/sort.gif b/testResult/html/jacoco-resources/sort.gif
    new file mode 100644
    index 0000000000000000000000000000000000000000..6757c2c32b57d768f3c12c4ae99a28bc32c9cbd7
    GIT binary patch
    literal 58
    zcmZ?wbhEHb<YC}qXkcX6uwldh|Nj+#vM_QnFf!;c00|xjP6h@h!JfpGjC*fB>i!bx
    N`t(%z_h<$NYXI&b5{m!;
    
    literal 0
    HcmV?d00001
    
    diff --git a/testResult/html/jacoco-resources/sort.js b/testResult/html/jacoco-resources/sort.js
    new file mode 100644
    index 000000000..9dcb0e8bb
    --- /dev/null
    +++ b/testResult/html/jacoco-resources/sort.js
    @@ -0,0 +1,148 @@
    +/*******************************************************************************
    + * Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
    + * This program and the accompanying materials are made available under
    + * the terms of the Eclipse Public License 2.0 which is available at
    + * http://www.eclipse.org/legal/epl-2.0
    + *
    + * SPDX-License-Identifier: EPL-2.0
    + *
    + * Contributors:
    + *    Marc R. Hoffmann - initial API and implementation
    + *
    + *******************************************************************************/
    +
    +(function () {
    +
    +  /**
    +   * Sets the initial sorting derived from the hash.
    +   *
    +   * @param linkelementids
    +   *          list of element ids to search for links to add sort inidcator
    +   *          hash links
    +   */
    +  function initialSort(linkelementids) {
    +    window.linkelementids = linkelementids;
    +    var hash = window.location.hash;
    +    if (hash) {
    +      var m = hash.match(/up-./);
    +      if (m) {
    +        var header = window.document.getElementById(m[0].charAt(3));
    +        if (header) {
    +          sortColumn(header, true);
    +        }
    +        return;
    +      }
    +      var m = hash.match(/dn-./);
    +      if (m) {
    +        var header = window.document.getElementById(m[0].charAt(3));
    +        if (header) {
    +          sortColumn(header, false);
    +        }
    +        return
    +      }
    +    }
    +  }
    +
    +  /**
    +   * Sorts the columns with the given header dependening on the current sort state.
    +   */
    +  function toggleSort(header) {
    +    var sortup = header.className.indexOf('down ') == 0;
    +    sortColumn(header, sortup);
    +  }
    +
    +  /**
    +   * Sorts the columns with the given header in the given direction.
    +   */
    +  function sortColumn(header, sortup) {
    +    var table = header.parentNode.parentNode.parentNode;
    +    var body = table.tBodies[0];
    +    var colidx = getNodePosition(header);
    +
    +    resetSortedStyle(table);
    +
    +    var rows = body.rows;
    +    var sortedrows = [];
    +    for (var i = 0; i < rows.length; i++) {
    +      r = rows[i];
    +      sortedrows[parseInt(r.childNodes[colidx].id.slice(1))] = r;
    +    }
    +
    +    var hash;
    +
    +    if (sortup) {
    +      for (var i = sortedrows.length - 1; i >= 0; i--) {
    +        body.appendChild(sortedrows[i]);
    +      }
    +      header.className = 'up ' + header.className;
    +      hash = 'up-' + header.id;
    +    } else {
    +      for (var i = 0; i < sortedrows.length; i++) {
    +        body.appendChild(sortedrows[i]);
    +      }
    +      header.className = 'down ' + header.className;
    +      hash = 'dn-' + header.id;
    +    }
    +
    +    setHash(hash);
    +  }
    +
    +  /**
    +   * Adds the sort indicator as a hash to the document URL and all links.
    +   */
    +  function setHash(hash) {
    +    window.document.location.hash = hash;
    +    ids = window.linkelementids;
    +    for (var i = 0; i < ids.length; i++) {
    +        setHashOnAllLinks(document.getElementById(ids[i]), hash);
    +    }
    +  }
    +
    +  /**
    +   * Extend all links within the given tag with the given hash.
    +   */
    +  function setHashOnAllLinks(tag, hash) {
    +    links = tag.getElementsByTagName("a");
    +    for (var i = 0; i < links.length; i++) {
    +        var a = links[i];
    +        var href = a.href;
    +        var hashpos = href.indexOf("#");
    +        if (hashpos != -1) {
    +            href = href.substring(0, hashpos);
    +        }
    +        a.href = href + "#" + hash;
    +    }
    +  }
    +
    +  /**
    +   * Calculates the position of a element within its parent.
    +   */
    +  function getNodePosition(element) {
    +    var pos = -1;
    +    while (element) {
    +      element = element.previousSibling;
    +      pos++;
    +    }
    +    return pos;
    +  }
    +
    +  /**
    +   * Remove the sorting indicator style from all headers.
    +   */
    +  function resetSortedStyle(table) {
    +    for (var c = table.tHead.firstChild.firstChild; c; c = c.nextSibling) {
    +      if (c.className) {
    +        if (c.className.indexOf('down ') == 0) {
    +          c.className = c.className.slice(5);
    +        }
    +        if (c.className.indexOf('up ') == 0) {
    +          c.className = c.className.slice(3);
    +        }
    +      }
    +    }
    +  }
    +
    +  window['initialSort'] = initialSort;
    +  window['toggleSort'] = toggleSort;
    +
    +})();
    diff --git a/testResult/html/jacoco-resources/source.gif b/testResult/html/jacoco-resources/source.gif
    new file mode 100644
    index 0000000000000000000000000000000000000000..b226e41c5276581db33d71525298ef572cc5d7ce
    GIT binary patch
    literal 354
    zcmZ?wbhEHb6krfwxXQrr`Rnf=KmWY@^y|~t-#>r-`SJ62+pK*(^ACOa@_X{KW3$$r
    zUbOlAiXE5N?74dH#gDtszu$lH{mGl3&)@xg`{~!`Z@=#VMPB~6_u~7*S3h2T`1$R}
    z?`Q9Re)#(P)3@JWfBgRb^LKTLe^s%6bxA;7sb4jaQ5?`-<<ng5TVLWgvEHM%)~l!1
    zYi_IS^d`3r{dQ}59F})EE$?<()ZzT#ME{lvwpTV~T-lU)Yj4ffO_~4y|7XAeia%Kx
    z85k@XbU-p7KQXY?ADC0%p(B)eLgkXi62W-^(!DQ#v2a~Gz-z9%&!+3h!38t#X02Ds
    zad;WPFvUVOY)YY2k84HG1kp%gVW!3wVI5ap$%?8ZHc4GqO=+PiQzvV>Y72H(vk7Xs
    us!1$fvP8{QU92ZrK%7tARasP&f6JDw8m_8J3W|I7DyXXX9C3DJum%7=h^`F)
    
    literal 0
    HcmV?d00001
    
    diff --git a/testResult/html/jacoco-resources/up.gif b/testResult/html/jacoco-resources/up.gif
    new file mode 100644
    index 0000000000000000000000000000000000000000..58ed21660ec467736a4d2af17d91341f7cfb556c
    GIT binary patch
    literal 67
    zcmZ?wbhEHb<YC}qSjfcSX{EDa!-oH0p!k!8k&A(eL5G2Xk%5PSlYxOrWJ=;nroA^G
    Ub$^Kz-Nct)ygK&ScM%3_0PmU?SpWb4
    
    literal 0
    HcmV?d00001
    
    diff --git a/testResult/html/jacoco-sessions.html b/testResult/html/jacoco-sessions.html
    new file mode 100644
    index 000000000..91eff16fe
    --- /dev/null
    +++ b/testResult/html/jacoco-sessions.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="jacoco-resources/report.gif" type="image/gif"/><title>Sessions</title></head><body><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="jacoco-sessions.html" class="el_session">Sessions</a></span><a href="index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_session">Sessions</span></div><h1>Sessions</h1><p>This coverage report is based on execution data from the following sessions:</p><table class="coverage" cellspacing="0"><thead><tr><td>Session</td><td>Start Time</td><td>Dump Time</td></tr></thead><tbody><tr><td><span class="el_session">wangkaipengdeMacBook-Pro.local-8ea31372</span></td><td>2025年4月19日 16:54:39</td><td>2025年4月19日 16:54:42</td></tr></tbody></table><p>Execution data for the following classes is considered in this report:</p><table class="coverage" cellspacing="0"><thead><tr><td>Class</td><td>Id</td></tr></thead><tbody><tr><td><span class="el_class">ch.qos.logback.classic.Level</span></td><td><code>9303df9e2a08f242</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.Logger</span></td><td><code>be6c3e45911cf8e2</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.LoggerContext</span></td><td><code>4512c2eff6c03c68</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.PatternLayout</span></td><td><code>5fe891de226cd5ba</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.encoder.PatternLayoutEncoder</span></td><td><code>adbb8c6e69fd1aeb</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.joran.JoranConfigurator</span></td><td><code>9df46b35e298dea1</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.joran.ModelClassToModelHandlerLinker</span></td><td><code>5446fc85f391468f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.joran.SerializedModelConfigurator</span></td><td><code>0370b98bcd206265</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.joran.action.ConfigurationAction</span></td><td><code>8c9da4cfd4a68c80</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.joran.action.LoggerAction</span></td><td><code>def920b8f3b7eac8</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.joran.action.RootLoggerAction</span></td><td><code>a0a60d59d8db3b11</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.joran.sanity.IfNestedWithinSecondPhaseElementSC</span></td><td><code>eabc6d5c0a18b7d0</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.model.ConfigurationModel</span></td><td><code>c09d15eff7bb1322</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.model.LoggerModel</span></td><td><code>d2da617f7d415704</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.model.RootLoggerModel</span></td><td><code>f5465abb75da4e3a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.model.processor.ConfigurationModelHandler</span></td><td><code>9fa11a3273fa70c9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.model.processor.ConfigurationModelHandlerFull</span></td><td><code>7a1dbab96810fa2f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.model.processor.LogbackClassicDefaultNestedComponentRules</span></td><td><code>11abc4ba781faf3f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.model.processor.LoggerModelHandler</span></td><td><code>a82763cafee16471</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.model.processor.RootLoggerModelHandler</span></td><td><code>0bdd0ddeee932ef7</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.ClassicConverter</span></td><td><code>ca6784b1cdac73e4</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.DateConverter</span></td><td><code>1122e9d1094bb161</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.EnsureExceptionHandling</span></td><td><code>c7ef7ced01cf2b40</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.LevelConverter</span></td><td><code>0ec63ea841fac8fa</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.LineSeparatorConverter</span></td><td><code>6bb3f1fbdd6806b9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.LoggerConverter</span></td><td><code>300f443c765acc98</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.MessageConverter</span></td><td><code>0688e46c25208e81</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.NamedConverter</span></td><td><code>800a7094fa870df9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.NamedConverter.CacheMissCalculator</span></td><td><code>cc0ea37245a48bd6</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.NamedConverter.NameCache</span></td><td><code>1175227c83b68cb9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.TargetLengthBasedClassNameAbbreviator</span></td><td><code>31fe90da1602830b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.ThreadConverter</span></td><td><code>b1a4a34d0dda551a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.ThrowableHandlingConverter</span></td><td><code>86f11ee7d86c38e3</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.pattern.ThrowableProxyConverter</span></td><td><code>e95e6657903e5c93</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.spi.Configurator.ExecutionStatus</span></td><td><code>cc40a5f533270748</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.spi.EventArgUtil</span></td><td><code>e0c9d11998766d79</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.spi.LogbackServiceProvider</span></td><td><code>6e02f42758dd8a54</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.spi.LoggerContextVO</span></td><td><code>fb6173d248f826d3</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.spi.LoggingEvent</span></td><td><code>8b7f71687e5d0c67</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.spi.TurboFilterList</span></td><td><code>42403a7d01f96dd1</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.util.ClassicEnvUtil</span></td><td><code>3e03f8adc0461ef2</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.util.ContextInitializer</span></td><td><code>7cfcfba69f8265bf</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.util.ContextInitializer.1</span></td><td><code>58fa6fb0dba0581d</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.util.DefaultJoranConfigurator</span></td><td><code>3da6a729c24e1784</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.util.LogbackMDCAdapter</span></td><td><code>f8e26313a025b32b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.classic.util.LoggerNameUtil</span></td><td><code>27bf8263ce12866e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.BasicStatusManager</span></td><td><code>d548b30535cbdd5b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.ConsoleAppender</span></td><td><code>a9d9302917577d23</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.ContextBase</span></td><td><code>a03a0249a0251838</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.CoreConstants</span></td><td><code>db8ef5527059aa3e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.FileAppender</span></td><td><code>bd98a0f78617a66f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.LayoutBase</span></td><td><code>36f6696d545dcad8</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.OutputStreamAppender</span></td><td><code>c33b4b3071b1682f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.PropertyDefinerBase</span></td><td><code>7b8da925f844a8f0</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.UnsynchronizedAppenderBase</span></td><td><code>895a29dbb896efbe</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.encoder.EncoderBase</span></td><td><code>c5b3872b99654c9b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.encoder.LayoutWrappingEncoder</span></td><td><code>c12e3595dcc95ae2</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.helpers.CyclicBuffer</span></td><td><code>aa4ceae09d045909</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.GenericXMLConfigurator</span></td><td><code>3afcabafc8dbbfb4</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.JoranConfiguratorBase</span></td><td><code>e6f9babdd2afdb1a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.JoranConstants</span></td><td><code>42ec1cbe011a94b9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.ModelClassToModelHandlerLinkerBase</span></td><td><code>88f3209fdcb1e1ca</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.Action</span></td><td><code>546ebd4341d0f33b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.ActionUtil</span></td><td><code>7e3fc5dd38557862</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.ActionUtil.Scope</span></td><td><code>1d4e9dbf1e45e938</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.AppenderAction</span></td><td><code>68d97b816bd3f2c4</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.AppenderRefAction</span></td><td><code>ee2935f9df24cc3b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.BaseModelAction</span></td><td><code>25a2e5084203d26d</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.DefinePropertyAction</span></td><td><code>ae6d45d71bcc61e0</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.ImcplicitActionDataForBasicProperty</span></td><td><code>00244c92478e63af</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.ImplicitModelAction</span></td><td><code>c18ffb4461a4cb17</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.ImplicitModelData</span></td><td><code>70230fc9a613a2d8</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.ImplicitModelDataForComplexProperty</span></td><td><code>8340d2b4e6783089</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.NOPAction</span></td><td><code>77adbbecb0e65657</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.action.PreconditionValidator</span></td><td><code>0c4a6adc6b568e45</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.event.BodyEvent</span></td><td><code>29cc9c4e511b9191</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.event.EndEvent</span></td><td><code>ef895d5661441d6e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.event.SaxEvent</span></td><td><code>cdc97cd84b098285</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.event.SaxEventRecorder</span></td><td><code>f8ba18c0bbde1295</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.event.StartEvent</span></td><td><code>fb8548f5eedf8490</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.sanity.AppenderWithinAppenderSanityChecker</span></td><td><code>3a526e37b4bd7905</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.sanity.SanityChecker</span></td><td><code>f5de2633569fa308</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.CAI_WithLocatorSupport</span></td><td><code>7bdc6d5391741963</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.ConfigurationWatchList</span></td><td><code>7fb4cca6c62e33d2</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.ConsoleTarget</span></td><td><code>2bfe78660d9c2361</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.ConsoleTarget.1</span></td><td><code>aed57c95030f1590</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.ConsoleTarget.2</span></td><td><code>3a02ebcd7664923a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.DefaultNestedComponentRegistry</span></td><td><code>916125aeea8f0f0f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.ElementPath</span></td><td><code>d18bd84952bfa796</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.ElementSelector</span></td><td><code>1cbb48a5f653b482</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.EventPlayer</span></td><td><code>75857d22259a9cf6</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.HostClassAndPropertyDouble</span></td><td><code>48e9dd9469fc067d</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.NoAutoStartUtil</span></td><td><code>8f961df3d3c297cb</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.SaxEventInterpretationContext</span></td><td><code>6f424f698ac36bec</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.SaxEventInterpreter</span></td><td><code>253ab4ffae94f3c4</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.spi.SimpleRuleStore</span></td><td><code>ae4803b963d5f15e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.AggregationAssessor</span></td><td><code>e2119b8864cd6570</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.ConfigurationWatchListUtil</span></td><td><code>e1b8a8b3b2817f29</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.ParentTag_Tag_Class_Tuple</span></td><td><code>548e4e5fc0664bb1</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.PropertySetter</span></td><td><code>5c6e947705823a29</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.StringToObjectConverter</span></td><td><code>daaf63c47688f8bd</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.beans.BeanDescription</span></td><td><code>46471ea64be92747</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.beans.BeanDescriptionCache</span></td><td><code>78dc010985ded39b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.beans.BeanDescriptionFactory</span></td><td><code>5c38dc71c2695812</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.joran.util.beans.BeanUtil</span></td><td><code>07e16ae2bc06396e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.AppenderModel</span></td><td><code>7880dcfe688ae31b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.AppenderRefModel</span></td><td><code>8f2b58e5aaf94330</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.ComponentModel</span></td><td><code>8d4dbe07b70da74d</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.DefineModel</span></td><td><code>5197baa1f698b14b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.ImplicitModel</span></td><td><code>995591db6a1a8a7a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.Model</span></td><td><code>284ee384dcd7d838</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.NamedComponentModel</span></td><td><code>e5af9879d6871e55</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.AllowAllModelFilter</span></td><td><code>3962b904772158de</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.AllowModelFilter</span></td><td><code>a770991e09edd5db</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.AppenderModelHandler</span></td><td><code>56401a98cb43d11d</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.AppenderRefDependencyAnalyser</span></td><td><code>5cb017a771174335</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.AppenderRefModelHandler</span></td><td><code>e1d3c7278ff6e990</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.ChainedModelFilter</span></td><td><code>ec98b56bcd257323</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.ChainedModelFilter.1</span></td><td><code>d05a303ee520c76d</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.DefaultProcessor</span></td><td><code>d759a87bb4f11c67</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.DefaultProcessor.1</span></td><td><code>a8724a2219fd187f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.DefineModelHandler</span></td><td><code>1e915f364a770623</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.DenyAllModelFilter</span></td><td><code>fb4f55cced67f234</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.DependencyDefinition</span></td><td><code>cbccbe0608f69a0a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.ImplicitModelHandler</span></td><td><code>c2027d3e57ae6b36</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.ImplicitModelHandler.1</span></td><td><code>07f3218d74b9a9e4</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.ModelHandlerBase</span></td><td><code>a38ad527989322a0</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.ModelInterpretationContext</span></td><td><code>65edb3d63b7b40fa</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.ProcessingPhase</span></td><td><code>8f6bba92873336c9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.processor.RefContainerDependencyAnalyser</span></td><td><code>c5f6269b5cc74857</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.model.util.VariableSubstitutionsHelper</span></td><td><code>53e3a039e202c9e7</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.net.ssl.SSLNestedComponentRegistryRules</span></td><td><code>69215774af93f98c</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.Converter</span></td><td><code>88fcb82d7ac22a16</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.ConverterUtil</span></td><td><code>20cf5be80690a434</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.DynamicConverter</span></td><td><code>fa0976090d3ec55e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.FormatInfo</span></td><td><code>c1091af50bf93789</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.FormattingConverter</span></td><td><code>c42fa317c19a9b78</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.LiteralConverter</span></td><td><code>6a26092f76c6ac93</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.PatternLayoutBase</span></td><td><code>d9c4f2841778cdc9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.PatternLayoutEncoderBase</span></td><td><code>c9dd29745e8a8c63</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.SpacePadder</span></td><td><code>c04e2e435b76b034</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.Compiler</span></td><td><code>69ac8e86a4de20e8</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.FormattingNode</span></td><td><code>5afdd38e3a828c01</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.Node</span></td><td><code>e9cbd0c1f07aa7d5</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.OptionTokenizer</span></td><td><code>0c054bdf6a570ef8</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.Parser</span></td><td><code>9b72c397f872fab3</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.SimpleKeywordNode</span></td><td><code>25a3f9e71b83475b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.Token</span></td><td><code>3ee8f94c73eb7f12</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.TokenStream</span></td><td><code>691b50c2d9f9756e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.parser.TokenStream.TokenizerState</span></td><td><code>00faf271c0eab6b9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.util.AlmostAsIsEscapeUtil</span></td><td><code>e875371c1c24f351</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.util.AsIsEscapeUtil</span></td><td><code>21a1cd41b6693952</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.util.RegularEscapeUtil</span></td><td><code>cb4169a6187c572f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.pattern.util.RestrictedEscapeUtil</span></td><td><code>8b21adafecce019f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.recovery.ResilientFileOutputStream</span></td><td><code>b8eb2a8b4d845659</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.recovery.ResilientOutputStreamBase</span></td><td><code>4d4e93743fece143</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.DefaultTimeBasedFileNamingAndTriggeringPolicy</span></td><td><code>54197521507dab0e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.RollingFileAppender</span></td><td><code>2c7dddf068d3132e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.RollingPolicyBase</span></td><td><code>d692bd3fa0d8082d</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.TimeBasedFileNamingAndTriggeringPolicyBase</span></td><td><code>78d0b9d900ee9c23</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.TimeBasedRollingPolicy</span></td><td><code>b4f0c39a1569cca9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.TriggeringPolicy</span></td><td><code>145196ab6435b03f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.CompressionMode</span></td><td><code>e29bc1696072e19a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.Compressor</span></td><td><code>98c00542c7245773</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.Compressor.1</span></td><td><code>44a2ec2610e93fd3</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.DateTokenConverter</span></td><td><code>610fb8cf9626ff59</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.FileFilterUtil</span></td><td><code>dd9b6882a96193b8</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.FileNamePattern</span></td><td><code>cb7b6c5c16e7c17f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.PeriodicityType</span></td><td><code>6f22932d3e3074da</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.RenameUtil</span></td><td><code>0183c47f66d56540</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.RollingCalendar</span></td><td><code>260d2d9b21601b91</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.RollingCalendar.1</span></td><td><code>50e8d188ca97d828</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.rolling.helper.TimeBasedArchiveRemover</span></td><td><code>2432c45f15f8103a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.spi.AppenderAttachableImpl</span></td><td><code>1ef122585612a073</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.spi.ConfigurationEvent</span></td><td><code>ce92914c253fb73a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.spi.ConfigurationEvent.EventType</span></td><td><code>aa15e3835e004950</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.spi.ContextAwareBase</span></td><td><code>78802b30b92ff289</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.spi.ContextAwareImpl</span></td><td><code>7c5f0060805cf148</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.spi.FilterAttachableImpl</span></td><td><code>1bdda09341cf5fb8</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.spi.FilterReply</span></td><td><code>0dabfae171683945</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.spi.LogbackLock</span></td><td><code>00146cd3b144dc92</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.status.InfoStatus</span></td><td><code>3ea5a04c41688d26</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.status.StatusBase</span></td><td><code>d2de3f7ff0e79b48</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.status.StatusUtil</span></td><td><code>bb63f76033b4fb59</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.Node</span></td><td><code>f4528f0aaf450327</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.Node.Type</span></td><td><code>5ada13b3bdafc4e1</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.NodeToStringTransformer</span></td><td><code>728598d08a340f09</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.NodeToStringTransformer.1</span></td><td><code>24b03a1fae54909b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.Parser</span></td><td><code>371a9da81929a41b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.Parser.1</span></td><td><code>ba5e2fe90977f204</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.Token</span></td><td><code>55429237cf121891</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.Token.Type</span></td><td><code>e596c92ff232595f</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.Tokenizer</span></td><td><code>e11eb06eb91626b6</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.subst.Tokenizer.TokenizerState</span></td><td><code>593d1de9c186ae02</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.AggregationType</span></td><td><code>01b742da2b7418b7</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.COWArrayList</span></td><td><code>5a1d0e670e55acd7</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.CachingDateFormatter</span></td><td><code>46ecbe497fb84c58</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.CachingDateFormatter.CacheTuple</span></td><td><code>4940f2769bff3196</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.CharSequenceState</span></td><td><code>7bab9a88737a0d25</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.CharSequenceToRegexMapper</span></td><td><code>13f9afadb9581a8e</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.ContextUtil</span></td><td><code>add59959d19a9e6b</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.DatePatternToRegexUtil</span></td><td><code>d193a092164014db</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.Duration</span></td><td><code>54cabe4f36e8e7a0</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.EnvUtil</span></td><td><code>eb2e1b9f3f7c24f6</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.FileSize</span></td><td><code>19500b5c04f84e19</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.FileUtil</span></td><td><code>c199bd26aef48045</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.Loader</span></td><td><code>da8ad7ce98f18557</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.OptionHelper</span></td><td><code>dc0fc1311dc9604a</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.StatusListenerConfigHelper</span></td><td><code>64584525acceb0ff</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.StatusPrinter</span></td><td><code>e1558319dba01961</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.StatusPrinter2</span></td><td><code>7ae81d2484f45fe9</code></td></tr><tr><td><span class="el_class">ch.qos.logback.core.util.StringUtil</span></td><td><code>29f38996e768ba8d</code></td></tr><tr><td><span class="el_class">com.esotericsoftware.kryo.io.Input</span></td><td><code>82caa4ac8d2c9ad6</code></td></tr><tr><td><span class="el_class">com.esotericsoftware.kryo.io.Output</span></td><td><code>2e152e7951e62ecf</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.ext.emoji.internal.EmojiReference</span></td><td><code>d142d56502c2f944</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.ext.emoji.internal.EmojiReference.Emoji</span></td><td><code>720cb5b3c1b35aea</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.ext.emoji.internal.EmojiShortcuts</span></td><td><code>9920842dbc4e9fde</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html.renderer.LinkStatus</span></td><td><code>477de5e303616105</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html.renderer.LinkType</span></td><td><code>ce3f6ae3a480dc56</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html.renderer.ResolvedLink</span></td><td><code>e12d7e14879b46ef</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.DelegatingNodeRendererFactoryWrapper</span></td><td><code>4e9d052168591392</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.ExtensionConversion</span></td><td><code>9496390ca449cbbd</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter</span></td><td><code>576b011f75dee0b0</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter.1</span></td><td><code>070c9189d6b1608e</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter.Builder</span></td><td><code>dda24997b9766831</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter.MainHtmlConverter</span></td><td><code>afb60620e341ae66</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter.MainHtmlConverter.SubHtmlNodeConverter</span></td><td><code>7422ce6cde1a5084</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.HtmlConverterCoreNodeRendererFactory</span></td><td><code>3fbda4630b8892a1</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.HtmlConverterOptions</span></td><td><code>e000813ea4097404</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.HtmlConverterPhase</span></td><td><code>fdcdc9330d0dbf1e</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.HtmlConverterState</span></td><td><code>f5cee9d16628c140</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.HtmlMarkdownWriter</span></td><td><code>626826b8a25185d3</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.HtmlNodeConverterSubContext</span></td><td><code>5b53e4e2bfe0746a</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.HtmlNodeRendererHandler</span></td><td><code>cc6700d83f678f08</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.LinkConversion</span></td><td><code>73063c57c49da1fa</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.ListState</span></td><td><code>eecad50bd5bac976</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.internal.HtmlConverterCoreNodeRenderer</span></td><td><code>1d858f5372ed03bd</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.html2md.converter.internal.HtmlConverterCoreNodeRenderer.1</span></td><td><code>dfc0d5fc620e7d81</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.builder.BuilderBase</span></td><td><code>4e5f735857b7a2b9</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.data.DataHolder</span></td><td><code>4a6392e3e3bf02de</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.data.DataKey</span></td><td><code>6c1404c6a2a9f8eb</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.data.DataKeyBase</span></td><td><code>1dc1ba4baa5320f0</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.data.DataSet</span></td><td><code>196c9b8bda8b2eb1</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.data.MutableDataSet</span></td><td><code>02baf6ee644501f7</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.data.NullableDataKey</span></td><td><code>971d14e951249990</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.data.ScopedDataSet</span></td><td><code>0b3e9c63f69009d1</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.data.SharedDataKeys</span></td><td><code>bf3f2a57ac90c4a2</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.dependency.DependencyResolver</span></td><td><code>e67545fad128cd5c</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.format.CharWidthProvider</span></td><td><code>9ee53f51f4e35dbb</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.format.CharWidthProvider.1</span></td><td><code>8ddba40d52dd0513</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.format.MarkdownWriterBase</span></td><td><code>c1cfb10e6eb36417</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.format.RomanNumeral</span></td><td><code>fd1e1ec9f715b03e</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.format.TableFormatOptions</span></td><td><code>840eb66c6d34d81a</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.format.TableManipulator</span></td><td><code>31919fdb7333c731</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.format.options.DiscretionaryText</span></td><td><code>1445784b5677386d</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.format.options.TableCaptionHandling</span></td><td><code>951a0268c3275f2c</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.html.AttributeImpl</span></td><td><code>c9c91c9d1d7f4840</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.html.Attributes</span></td><td><code>df0b717688b7ae7a</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.html.CellAlignment</span></td><td><code>351851a148626fc8</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.html.MutableAttributes</span></td><td><code>0ac7f7704248d5c9</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.misc.BitFieldSet</span></td><td><code>293f7f44c2640a25</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.misc.BitFieldSet.UniverseLoader</span></td><td><code>48c3ab9042708cb1</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.misc.CharPredicate</span></td><td><code>36c9c8e944a33ebd</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.misc.CharPredicate.1</span></td><td><code>8e20e7930d779083</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.misc.Extension</span></td><td><code>14a7ae4f660669a7</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.misc.Pair</span></td><td><code>eeb038616fec9e8a</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.misc.Ref</span></td><td><code>6ccd970fafb38c91</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.misc.Utils</span></td><td><code>3d8dfc680c10faba</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.BasedSequence</span></td><td><code>63542754e3e5fab6</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.BasedSequence.EmptyBasedSequence</span></td><td><code>bedc77ffe587211f</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.BasedSequenceImpl</span></td><td><code>0bbba4c12be1831e</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.CharSubSequence</span></td><td><code>2accf77e36a633be</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.IRichSequenceBase</span></td><td><code>1d53cf121d722e7f</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.LineAppendable</span></td><td><code>12964fda46d5d09a</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.LineAppendable.Options</span></td><td><code>4181bcfa6a0058fd</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.LineAppendableImpl</span></td><td><code>9125fa41b2c5ff2a</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.LineInfo</span></td><td><code>e3550da9080ea831</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.LineInfo.Flags</span></td><td><code>365754eede38cd90</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.LineInfo.Preformatted</span></td><td><code>4d4b060c73118188</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.Range</span></td><td><code>e385ef66922e9880</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.RepeatedSequence</span></td><td><code>51781b1d3d005dad</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.SequenceUtils</span></td><td><code>ebb603e730f101f6</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.SubSequence</span></td><td><code>45389a6728406b61</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.builder.ISequenceBuilder</span></td><td><code>5262a0ec9127e588</code></td></tr><tr><td><span class="el_class">com.vladsch.flexmark.util.sequence.builder.StringSequenceBuilder</span></td><td><code>91d60dce173a4991</code></td></tr><tr><td><span class="el_class">io.github.pixee.security.BoundedLineReader</span></td><td><code>c21a2fc48873e62c</code></td></tr><tr><td><span class="el_class">io.github.pixee.security.BoundedLineReader.BufferedReaderWrapper</span></td><td><code>f4e8f75158b4c659</code></td></tr><tr><td><span class="el_class">io.github.pixee.security.Filenames</span></td><td><code>590dca527e73f5aa</code></td></tr><tr><td><span class="el_class">net.bytebuddy.ByteBuddy</span></td><td><code>838d546c74c04603</code></td></tr><tr><td><span class="el_class">net.bytebuddy.ClassFileVersion</span></td><td><code>dd2a74a492f79607</code></td></tr><tr><td><span class="el_class">net.bytebuddy.ClassFileVersion.VersionLocator.Resolved</span></td><td><code>18852aceed981f99</code></td></tr><tr><td><span class="el_class">net.bytebuddy.ClassFileVersion.VersionLocator.Resolver</span></td><td><code>257736a9b1815cab</code></td></tr><tr><td><span class="el_class">net.bytebuddy.NamingStrategy.AbstractBase</span></td><td><code>47d6d27e15064a2b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.NamingStrategy.Suffixing</span></td><td><code>f5ee3e4bc4be83df</code></td></tr><tr><td><span class="el_class">net.bytebuddy.NamingStrategy.Suffixing.BaseNameResolver.ForUnnamedType</span></td><td><code>dc4c226cbd0897a8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.NamingStrategy.SuffixingRandom</span></td><td><code>c63322e16e2c0a80</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache</span></td><td><code>05243229e50ea1b0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache.LookupKey</span></td><td><code>599592f0f74bbe07</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache.SimpleKey</span></td><td><code>037370c46d07d669</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache.Sort</span></td><td><code>2863b0d48a0f3008</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache.Sort.1</span></td><td><code>ba4f2805581e1090</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache.Sort.2</span></td><td><code>db80a28c74867927</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache.Sort.3</span></td><td><code>65abff93a29d75a2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache.StorageKey</span></td><td><code>b7a810d4119627f9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.TypeCache.WithInlineExpunction</span></td><td><code>131bdbf6b155e141</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent</span></td><td><code>1706dbc7cc948682</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AgentProvider.ForByteBuddyAgent</span></td><td><code>36b743b1a180b9b2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider</span></td><td><code>25fc112ec4241820</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.Accessor.ExternalAttachment</span></td><td><code>01900040d56a33a9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.Accessor.Simple</span></td><td><code>24a16fe45a35cb7e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.Accessor.Simple.WithExternalAttachment</span></td><td><code>e50452f415c0d0d5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.Compound</span></td><td><code>e5e7150b3c9950e7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.ForEmulatedAttachment</span></td><td><code>aaaaff9f110644cf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.ForJ9Vm</span></td><td><code>b481ac0aa3eb0f45</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.ForModularizedVm</span></td><td><code>291692854fc485db</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.ForStandardToolsJarVm</span></td><td><code>72a57f4a88fe5b11</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentProvider.ForUserDefinedToolsJar</span></td><td><code>9d3c4b5606b23681</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentTypeEvaluator.ForJava9CapableVm</span></td><td><code>37b89b5a7cbc17bf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.AttachmentTypeEvaluator.InstallationAction</span></td><td><code>3d6e7788b8164c7c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.ProcessProvider.ForCurrentVm</span></td><td><code>9d766ca8cd412b2d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.ByteBuddyAgent.ProcessProvider.ForCurrentVm.ForJava9CapableVm</span></td><td><code>f4195e3947757962</code></td></tr><tr><td><span class="el_class">net.bytebuddy.agent.Installer</span></td><td><code>8bf7b5c7d2be2c5c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice</span></td><td><code>f305e139e05d37c5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.AdviceVisitor</span></td><td><code>357f894a41ceff14</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.AdviceVisitor.WithExitAdvice</span></td><td><code>68fa3e21c337c5fa</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.AdviceVisitor.WithExitAdvice.WithoutExceptionHandling</span></td><td><code>c7bd20d318c941ad</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ArgumentHandler.Factory</span></td><td><code>3444761fb90c495b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ArgumentHandler.Factory.1</span></td><td><code>1d2beab8f1366c9a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ArgumentHandler.Factory.2</span></td><td><code>11bce7a1125feea8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ArgumentHandler.ForAdvice.Default</span></td><td><code>ec6406cb405bf092</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ArgumentHandler.ForAdvice.Default.ForMethodEnter</span></td><td><code>a474e82610073bf8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ArgumentHandler.ForAdvice.Default.ForMethodExit</span></td><td><code>3cb294a34e216bfc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ArgumentHandler.ForInstrumentedMethod.Default</span></td><td><code>b654aaafdaff669e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ArgumentHandler.ForInstrumentedMethod.Default.Copying</span></td><td><code>dd71cb95b808912c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Delegator.ForRegularInvocation.Factory</span></td><td><code>dc9cbc0460451f68</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher</span></td><td><code>1faecb04745bcf65</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inactive</span></td><td><code>63eda3c0a6838aa0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining</span></td><td><code>ac2e9b946bc5547c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.CodeTranslationVisitor</span></td><td><code>1d10073ec77cb752</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved</span></td><td><code>f119094b90ae061a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved.AdviceMethodInliner</span></td><td><code>edc78a4951c00fe1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved.AdviceMethodInliner.ExceptionTableCollector</span></td><td><code>7926ac79f840cadf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved.AdviceMethodInliner.ExceptionTableExtractor</span></td><td><code>d7f4d8e4807ac7b9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved.AdviceMethodInliner.ExceptionTableSubstitutor</span></td><td><code>60510f39bb96d2d5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved.ForMethodEnter</span></td><td><code>3e9ccebcb7aa9051</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved.ForMethodEnter.WithRetainedEnterType</span></td><td><code>6fe5123a78babee2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved.ForMethodExit</span></td><td><code>893668ff2f906aeb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Inlining.Resolved.ForMethodExit.WithoutExceptionHandler</span></td><td><code>fa730e4edbedad18</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.Disabled</span></td><td><code>5606a90fa94b0407</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForType</span></td><td><code>87f7b1189c46db9e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue</span></td><td><code>d2da0b78df827519</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.1</span></td><td><code>0695a1ff7c4d7e11</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.2</span></td><td><code>cc05b0e468a082a4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.3</span></td><td><code>a9d3fed55a37a5e3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.4</span></td><td><code>a2c9ea6d513a32f3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.5</span></td><td><code>ddc974ff1faaae14</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.6</span></td><td><code>d7e44be01c05cfc8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.7</span></td><td><code>c35af7c4804fd642</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.8</span></td><td><code>a229da4acc4fee12</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.9</span></td><td><code>d9c260cbf2f6e5c5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.Bound</span></td><td><code>3690a986936c8865</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.ForValue.OfNonDefault</span></td><td><code>5c14d8da70fa89ad</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.RelocationHandler.Relocation.ForLabel</span></td><td><code>ee713d68468cc862</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.Resolved.AbstractBase</span></td><td><code>80b170aa8a65cfd9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.SuppressionHandler.NoOp</span></td><td><code>d513f61a1d183438</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.Dispatcher.SuppressionHandler.Suppressing</span></td><td><code>911cb9fa0e684a34</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ExceptionHandler.Default</span></td><td><code>3f70c9a32dd19273</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ExceptionHandler.Default.1</span></td><td><code>076ebc8083c04a6d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ExceptionHandler.Default.2</span></td><td><code>482b1b8707a28273</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.ExceptionHandler.Default.3</span></td><td><code>f6dcfff6cf5a06da</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.MethodSizeHandler.Default</span></td><td><code>f7f8244528450338</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.MethodSizeHandler.Default.ForAdvice</span></td><td><code>e1cdb8e02bcd48be</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.MethodSizeHandler.Default.WithCopiedArguments</span></td><td><code>bdf993c324acd582</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.NoExceptionHandler</span></td><td><code>6880c478a3d579f7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Factory.AdviceType</span></td><td><code>128546e4c927d7cd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Factory.Illegal</span></td><td><code>7021ad523c9564fe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForAllArguments</span></td><td><code>65a33305620c219e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForAllArguments.Factory</span></td><td><code>b8fd006bdcd8fade</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForArgument</span></td><td><code>b488ed5f3ae753ce</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForArgument.Unresolved</span></td><td><code>d12176878187707b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForArgument.Unresolved.Factory</span></td><td><code>6a49f4c64d0d58e2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForEnterValue</span></td><td><code>4f1e22a8fbb46e55</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForEnterValue.Factory</span></td><td><code>adbbfc15c48310a4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForExitValue.Factory</span></td><td><code>674cc41645ca3ab4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForField.Unresolved.Factory</span></td><td><code>d1a350e7a720461a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForFieldHandle.Unresolved.ReaderFactory</span></td><td><code>b5309ad9e74354fb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForFieldHandle.Unresolved.WriterFactory</span></td><td><code>e1907193700ca775</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForInstrumentedMethod</span></td><td><code>11056d8ec4a1fb6a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForInstrumentedMethod.1</span></td><td><code>5334f224807db7de</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForInstrumentedMethod.2</span></td><td><code>f939114a04039e6c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForInstrumentedMethod.3</span></td><td><code>87d8cea7bdab8c09</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForInstrumentedMethod.4</span></td><td><code>132bd6e0c209cb56</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForInstrumentedMethod.5</span></td><td><code>88ed65cbc1031507</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForInstrumentedType</span></td><td><code>d04a8ad0b01d6446</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForLocalValue.Factory</span></td><td><code>11b67a70d619051b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForOrigin.Factory</span></td><td><code>fbd3c8da2834f747</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForReturnValue</span></td><td><code>6acd257a7a383935</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForReturnValue.Factory</span></td><td><code>f86500d76a160cde</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForSelfCallHandle.Factory</span></td><td><code>7278027c3bbffc1d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForStackManipulation</span></td><td><code>2f2d0bb86220e951</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForStackManipulation.Factory</span></td><td><code>d5559e6c68f30189</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForStubValue</span></td><td><code>3bc868a11728ab93</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForThisReference</span></td><td><code>8286ae4679f66f0a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForThisReference.Factory</span></td><td><code>6df59fdea3ece77b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForThrowable.Factory</span></td><td><code>383fe8f8f171fc6b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.ForUnusedValue.Factory</span></td><td><code>ca2319139c33abeb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Sort</span></td><td><code>354f3667c80d8970</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Sort.1</span></td><td><code>6d65ef7e0a39666e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Sort.2</span></td><td><code>aae66217726345fa</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Target.ForArray</span></td><td><code>239a35988cc401b1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Target.ForArray.ReadOnly</span></td><td><code>89314c935ed614a6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Target.ForDefaultValue</span></td><td><code>ab3c2c16f4e29eea</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Target.ForDefaultValue.ReadWrite</span></td><td><code>f5202ce81405e2e9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Target.ForStackManipulation</span></td><td><code>9f0ec9f43a70365b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Target.ForVariable</span></td><td><code>ef907366ca01da12</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Target.ForVariable.ReadOnly</span></td><td><code>70150883df8e0e05</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.OffsetMapping.Target.ForVariable.ReadWrite</span></td><td><code>6d3b4a977fa51896</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.PostProcessor.NoOp</span></td><td><code>25e4dfd1d4422b79</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default</span></td><td><code>f3d57aa0d77ad487</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.ForAdvice</span></td><td><code>61f37733e1f7b3d8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.Initialization</span></td><td><code>2c5c4e5b859f14dc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.Initialization.1</span></td><td><code>a3d085b29385e3c8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.Initialization.2</span></td><td><code>ed62149a54901f99</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.TranslationMode</span></td><td><code>c8b35f2b284c8c58</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.TranslationMode.1</span></td><td><code>3bd6aef20f26c4f8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.TranslationMode.2</span></td><td><code>b806f36223c864f8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.TranslationMode.3</span></td><td><code>bdbe57cf3cd49a5a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.WithPreservedArguments</span></td><td><code>2da1bfa0abeae4c8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.StackMapFrameHandler.Default.WithPreservedArguments.WithArgumentCopy</span></td><td><code>9db9d30d886b8212</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.Advice.WithCustomMapping</span></td><td><code>f324626c369c4fd1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.AsmVisitorWrapper.AbstractBase</span></td><td><code>1e9138fb1b0b4185</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.AsmVisitorWrapper.Compound</span></td><td><code>945ba66141d2cde6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods</span></td><td><code>a7770ccc1cc13150</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods.DispatchingVisitor</span></td><td><code>6468c29bcf0a7088</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods.Entry</span></td><td><code>372f0fa4632a2bfe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.AsmVisitorWrapper.NoOp</span></td><td><code>6782431026fffec0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.MemberRemoval</span></td><td><code>7699af1aa63a055f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.asm.MemberRemoval.MemberRemovingClassVisitor</span></td><td><code>14fd1af1bcc05173</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.ByteCodeElement.Token.TokenList</span></td><td><code>c378dffbca959a48</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.ModifierReviewable.AbstractBase</span></td><td><code>2ea5306ed3260bac</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.NamedElement.WithDescriptor</span></td><td><code>79917fb7c151850b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.TypeVariableSource.AbstractBase</span></td><td><code>afa9cbe714a69ea6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationDescription</span></td><td><code>7b5d7f348452a8fd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationDescription.AbstractBase</span></td><td><code>7e8a08425beaaf44</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationDescription.ForLoadedAnnotation</span></td><td><code>2fc93c57331daaa3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationList.AbstractBase</span></td><td><code>b2574970a6108617</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationList.Empty</span></td><td><code>85778debddc6a8e2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationList.Explicit</span></td><td><code>45fb4e2b44a1ab64</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationList.ForLoadedAnnotations</span></td><td><code>309c064a5edad9f1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue</span></td><td><code>d7f3d011bdffb51e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.AbstractBase</span></td><td><code>69e2bb699468284a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant</span></td><td><code>ecbcbaccbfb168ea</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType</span></td><td><code>42f42342648b8fa7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.1</span></td><td><code>de1b42a4850c814e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.2</span></td><td><code>2062c5f9436afbda</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.3</span></td><td><code>9f54ef11aadacf72</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.4</span></td><td><code>994767655151955f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.5</span></td><td><code>1745bb6a04e8993d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.6</span></td><td><code>e1f9c9a005abae22</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.7</span></td><td><code>6ed2f2d151367cbb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.8</span></td><td><code>a27ee00384a36bef</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForConstant.PropertyDelegate.ForNonArrayType.9</span></td><td><code>63c6c63b84353202</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForDescriptionArray</span></td><td><code>031780a89d891e4d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForEnumerationDescription</span></td><td><code>ae910611763d0f68</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.ForTypeDescription</span></td><td><code>6ab0ae6789cbf5ef</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.Sort</span></td><td><code>0a66a94600ced3bc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.annotation.AnnotationValue.State</span></td><td><code>ec800f741ddcd502</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.enumeration.EnumerationDescription.AbstractBase</span></td><td><code>70a8d1f3fd28fd3f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.enumeration.EnumerationDescription.ForLoadedEnumeration</span></td><td><code>affebde010d964e1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldDescription.AbstractBase</span></td><td><code>95705cfbb95c2b53</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldDescription.ForLoadedField</span></td><td><code>14a5417e3953886c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldDescription.InDefinedShape.AbstractBase</span></td><td><code>0d8b55a89d1f5d33</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldDescription.Latent</span></td><td><code>3988698b9a06a78b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldDescription.Token</span></td><td><code>bd8d67b2812b2126</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldList.AbstractBase</span></td><td><code>eaf7adc9c2f91154</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldList.Explicit</span></td><td><code>8f12c029b0e18281</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldList.ForLoadedFields</span></td><td><code>8b99118397373efd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.field.FieldList.ForTokens</span></td><td><code>212571f44e74f5ee</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription</span></td><td><code>29fc82bada408f50</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.AbstractBase</span></td><td><code>cc2ea0dc1bd6aa8a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.ForLoadedConstructor</span></td><td><code>857bcf04a410f44a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.ForLoadedMethod</span></td><td><code>306abc0e9f803174</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.InDefinedShape.AbstractBase</span></td><td><code>a1150ad60ccc435a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.InDefinedShape.AbstractBase.ForLoadedExecutable</span></td><td><code>bc47af559741f006</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.Latent</span></td><td><code>906c68aeeaaf7c2c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.Latent.TypeInitializer</span></td><td><code>d2d992bea0797b42</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.SignatureToken</span></td><td><code>07cceea807b4c373</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.Token</span></td><td><code>c396fe5e09df11f0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.TypeSubstituting</span></td><td><code>0993d75a33526eb0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodDescription.TypeToken</span></td><td><code>a7a7a20b68553468</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodList.AbstractBase</span></td><td><code>6e4bb8d5a09ca7f3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodList.Explicit</span></td><td><code>9650ca53b2f6a6e2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodList.ForLoadedMethods</span></td><td><code>7648140277a32974</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodList.ForTokens</span></td><td><code>42b106ad57e53b91</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.MethodList.TypeSubstituting</span></td><td><code>e3ff3658a2c85bc8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.AbstractBase</span></td><td><code>a394065e412d9f2b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.ForLoadedParameter</span></td><td><code>50471d202a211145</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.ForLoadedParameter.OfConstructor</span></td><td><code>c6cfab06736c11d7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.ForLoadedParameter.OfMethod</span></td><td><code>4bdfdb69b0fecfa2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.InDefinedShape.AbstractBase</span></td><td><code>607a2c5720c1d99a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.Latent</span></td><td><code>be72fd24f033a8d0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.Token</span></td><td><code>27e1db3c533eaa72</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.Token.TypeList</span></td><td><code>c866bbd9b65b2971</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterDescription.TypeSubstituting</span></td><td><code>0a2f4fe45f35ee10</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterList.AbstractBase</span></td><td><code>713c270249b385e6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterList.Empty</span></td><td><code>8a77392f505a85b6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterList.ForLoadedExecutable</span></td><td><code>a932c4315d445096</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterList.ForLoadedExecutable.OfConstructor</span></td><td><code>884be9c14d50eac9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterList.ForLoadedExecutable.OfMethod</span></td><td><code>863355ac9b305941</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterList.ForTokens</span></td><td><code>9205617d32f4ed0f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.method.ParameterList.TypeSubstituting</span></td><td><code>3ad933fe68b4ba20</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.modifier.ModifierContributor.Resolver</span></td><td><code>aa79f438ed4bdb0e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.modifier.TypeManifestation</span></td><td><code>b090b1e7c7385c73</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.modifier.Visibility</span></td><td><code>98008a87e5e30e3e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.modifier.Visibility.1</span></td><td><code>b8d629c2c45ceb7b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.PackageDescription.AbstractBase</span></td><td><code>0ec6f311394275e0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.PackageDescription.ForLoadedPackage</span></td><td><code>4c0675dabc7f0352</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.PackageDescription.Simple</span></td><td><code>f70620e1fa6685ee</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.RecordComponentList.AbstractBase</span></td><td><code>6ec7cebb7657a89e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.RecordComponentList.Empty</span></td><td><code>8a33bff3c9e86862</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.RecordComponentList.ForTokens</span></td><td><code>fc914dd18d7a6558</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDefinition.Sort</span></td><td><code>3632cae0b8b85e30</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription</span></td><td><code>c3eaeda62d963b8f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.AbstractBase</span></td><td><code>cb297577c55823f8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.AbstractBase.OfSimpleType</span></td><td><code>1589d32ddb001834</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.ArrayProjection</span></td><td><code>47de01eff8b325e5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.ForLoadedType</span></td><td><code>c33a8607361c7796</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic</span></td><td><code>52e171d973daa9cb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AbstractBase</span></td><td><code>a3d2c29e6d2a3fcd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator</span></td><td><code>9c028b3c12c33b91</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.Chained</span></td><td><code>154b3ba7acebcc75</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.ForLoadedExecutableExceptionType</span></td><td><code>28af2b8d9c34ea76</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.ForLoadedExecutableParameterType</span></td><td><code>3a0e02b073c01467</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.ForLoadedField</span></td><td><code>b412ff8d864700cd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.ForLoadedInterface</span></td><td><code>fb194b3b939613b4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.ForLoadedMethodReturnType</span></td><td><code>f5875cdcd78c2bff</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.ForLoadedSuperClass</span></td><td><code>fd02837b7b518950</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.ForLoadedTypeVariable</span></td><td><code>b3762ed8f452d001</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.Delegator.Simple</span></td><td><code>97601d5f7d8f7383</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.ForComponentType</span></td><td><code>b4c4d3d8bff2e639</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.ForOwnerType</span></td><td><code>7a2dbca0003caaa3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.ForTypeArgument</span></td><td><code>b5bacbcb4e45002d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.ForTypeVariableBoundType</span></td><td><code>24d7c49a73a6b8e3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.ForTypeVariableBoundType.OfFormalTypeVariable</span></td><td><code>0a22bcb0c62e0eb0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.ForWildcardUpperBoundType</span></td><td><code>3b23356e8b6c548f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.AnnotationReader.NoOp</span></td><td><code>873bbc80e4c7f528</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection</span></td><td><code>1bc1668efd2f43ea</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.ForLoadedFieldType</span></td><td><code>8d38397f64955e4f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.ForLoadedReturnType</span></td><td><code>711634496c98916a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.ForLoadedSuperClass</span></td><td><code>ae46dea7aba001e4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.OfConstructorParameter</span></td><td><code>ea93494df173123c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.OfMethodParameter</span></td><td><code>f3c910c0777a1f5f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.WithEagerNavigation</span></td><td><code>acd190d131335940</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.WithEagerNavigation.OfAnnotatedElement</span></td><td><code>230aabc191d3d5ae</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.WithLazyNavigation</span></td><td><code>6dd755811aed5f76</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.WithLazyNavigation.OfAnnotatedElement</span></td><td><code>19d69a93fb3da4e7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProjection.WithResolvedErasure</span></td><td><code>c79771af451939ae</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.LazyProxy</span></td><td><code>66ac1de2ca80db17</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfGenericArray</span></td><td><code>ac56f079577762b7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfGenericArray.ForLoadedType</span></td><td><code>4007d9134ca42138</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfGenericArray.Latent</span></td><td><code>3b06658f4d0c50aa</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfNonGenericType</span></td><td><code>652d2c9db69ed540</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfNonGenericType.ForErasure</span></td><td><code>afb671c2f7429be8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfNonGenericType.ForLoadedType</span></td><td><code>b39cfd8dd965a17b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfNonGenericType.Latent</span></td><td><code>d83d51044b689b7b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfParameterizedType</span></td><td><code>952bb9c7ba1e662c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfParameterizedType.ForGenerifiedErasure</span></td><td><code>10482ce0c8c4836d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfParameterizedType.ForLoadedType</span></td><td><code>ddebde8a4f256ce1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfParameterizedType.ForLoadedType.ParameterArgumentTypeList</span></td><td><code>80c72250236cfb5d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfParameterizedType.Latent</span></td><td><code>0db9c96ff9d259d4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfTypeVariable</span></td><td><code>30d80ee4384dbc09</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfTypeVariable.ForLoadedType</span></td><td><code>b07768fd5f88ff15</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfTypeVariable.ForLoadedType.TypeVariableBoundList</span></td><td><code>83310e1e975b0eae</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfTypeVariable.Symbolic</span></td><td><code>caadd4da4bdb3ff0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfTypeVariable.WithAnnotationOverlay</span></td><td><code>b760376bf570c6f3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfWildcardType</span></td><td><code>0bf0414fb42587d0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfWildcardType.ForLoadedType</span></td><td><code>f56696c3d3eb5f04</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfWildcardType.ForLoadedType.WildcardLowerBoundTypeList</span></td><td><code>eaaffd42a771ed92</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfWildcardType.ForLoadedType.WildcardUpperBoundTypeList</span></td><td><code>498e3419cc1464e4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.OfWildcardType.Latent</span></td><td><code>919372c8fbb02b12</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.ForRawType</span></td><td><code>3af97c0265f25a9c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.ForSignatureVisitor</span></td><td><code>9babb7ff10d9cd6c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Reducing</span></td><td><code>35449bf4f454303d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Reifying</span></td><td><code>0bb1eb2e68e20d64</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Reifying.1</span></td><td><code>2a86a87341257a71</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Reifying.2</span></td><td><code>383e688cfe742ceb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Substitutor</span></td><td><code>7ef6dcbaba945e26</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Substitutor.ForAttachment</span></td><td><code>9184888bf6e57489</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Substitutor.ForDetachment</span></td><td><code>ec869caf3d1ab41e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Validator</span></td><td><code>3a7019a8a865fea7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Validator.1</span></td><td><code>3ea3c8fbe931c1bf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Validator.2</span></td><td><code>72639179963e541e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Validator.3</span></td><td><code>1607082a559ffaf8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.Generic.Visitor.Validator.ForTypeAnnotations</span></td><td><code>56297b4a76798d9b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeDescription.LazyProxy</span></td><td><code>c4b689525920fdc9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList</span></td><td><code>f06f77875f72c810</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.AbstractBase</span></td><td><code>2f5f59e7819a0a7f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Empty</span></td><td><code>280abad669579caa</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Explicit</span></td><td><code>4c267415b577b529</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.ForLoadedTypes</span></td><td><code>11848d6d86b894f2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.AbstractBase</span></td><td><code>82829a39c7e83949</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.Empty</span></td><td><code>eb0d06f0d84a10d7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.Explicit</span></td><td><code>dbf63fc7e1cb42c6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.ForDetachedTypes</span></td><td><code>9215758b2898ce6d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.ForDetachedTypes.OfTypeVariables</span></td><td><code>00852bcf611b458a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.ForDetachedTypes.OfTypeVariables.AttachedTypeVariable</span></td><td><code>74253324bfb4dade</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.ForDetachedTypes.WithResolvedErasure</span></td><td><code>a4b8b4a98fab68a2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.ForLoadedTypes</span></td><td><code>2db26d759624cfcf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.ForLoadedTypes.OfTypeVariables</span></td><td><code>32168b9503975629</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.OfConstructorExceptionTypes</span></td><td><code>af227031fb6a48a2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.OfLoadedInterfaceTypes</span></td><td><code>3dd7d88021ba39ef</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.OfLoadedInterfaceTypes.TypeProjection</span></td><td><code>5df5d5822be7a771</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.OfMethodExceptionTypes</span></td><td><code>75320ed4ea3f9e8b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeList.Generic.OfMethodExceptionTypes.TypeProjection</span></td><td><code>2164666d4f04e94d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.description.type.TypeVariableToken</span></td><td><code>2b23466727d53c13</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.ClassFileLocator.ForClassLoader</span></td><td><code>88686ad269dc51ee</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.ClassFileLocator.ForClassLoader.BootLoaderProxyCreationAction</span></td><td><code>cd2788dbef627a42</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.ClassFileLocator.Resolution.Explicit</span></td><td><code>22bfaf7841f9cfe7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.ClassFileLocator.Simple</span></td><td><code>52d9cdf7356ec2f3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.AbstractBase</span></td><td><code>9b38b41031ddd537</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase</span></td><td><code>aa1759a3194d7f14</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase.Adapter</span></td><td><code>8d3da14521e1196b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase.Adapter.MethodDefinitionAdapter</span></td><td><code>140fff7546e61ef6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase.Adapter.MethodDefinitionAdapter.AnnotationAdapter</span></td><td><code>e497b02f08dd88b8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase.Adapter.MethodDefinitionAdapter.SimpleParameterAnnotationAdapter</span></td><td><code>383c794e49b347bf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase.Adapter.MethodMatchAdapter</span></td><td><code>41bb906a46cbef6c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase.Adapter.MethodMatchAdapter.AnnotationAdapter</span></td><td><code>10fac75e163103af</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase.Delegator</span></td><td><code>936b4705c2af1791</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.AbstractBase.UsingTypeWriter</span></td><td><code>fd88ea62d3bec9e8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.AbstractBase</span></td><td><code>e1b17723ee466981</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.AbstractBase.Adapter</span></td><td><code>67e8dfcfa03c64c4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ExceptionDefinition.AbstractBase</span></td><td><code>553558cdd2ea7d30</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ImplementationDefinition.AbstractBase</span></td><td><code>72bbf9be43329300</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ParameterDefinition.AbstractBase</span></td><td><code>c46baf31879e7f18</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ParameterDefinition.Initial.AbstractBase</span></td><td><code>bf9ad000af8dfbe7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ParameterDefinition.Simple.AbstractBase</span></td><td><code>3591bd96cf8ba88c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ParameterDefinition.Simple.Annotatable.AbstractBase</span></td><td><code>dd4ec10b2ca5aed6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ParameterDefinition.Simple.Annotatable.AbstractBase.Adapter</span></td><td><code>511cf7967b5892fe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.ReceiverTypeDefinition.AbstractBase</span></td><td><code>6abfb4279099921b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Builder.MethodDefinition.TypeVariableDefinition.AbstractBase</span></td><td><code>9bb9648ea001ee26</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Default</span></td><td><code>def7a671eae8aff3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Default.Loaded</span></td><td><code>a513136828111926</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.DynamicType.Default.Unloaded</span></td><td><code>e84874d7b92b18c6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.TargetType</span></td><td><code>92d2ae273e27a92e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.Transformer.NoOp</span></td><td><code>1a602ca33b851811</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.TypeResolutionStrategy.Passive</span></td><td><code>560bec77a3a9e1cd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.VisibilityBridgeStrategy.Default</span></td><td><code>92dd499e424995ab</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.VisibilityBridgeStrategy.Default.1</span></td><td><code>88969522be51c47a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.VisibilityBridgeStrategy.Default.2</span></td><td><code>e90257accee75dbe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.VisibilityBridgeStrategy.Default.3</span></td><td><code>dba1eec57628b9b8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader</span></td><td><code>0060f7921b960b6f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader.ClassDefinitionAction</span></td><td><code>e532c9b6eed0ff8a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader.PackageLookupStrategy.CreationAction</span></td><td><code>579d717b5fb1777c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader.PackageLookupStrategy.ForJava9CapableVm</span></td><td><code>90efd9ed692084a0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader.PersistenceHandler</span></td><td><code>e4d3975d11965972</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader.PersistenceHandler.1</span></td><td><code>c5b4f188a059f348</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader.PersistenceHandler.2</span></td><td><code>d4d9d89c20c33c79</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader.SynchronizationStrategy.CreationAction</span></td><td><code>85defb9db5b2b5cc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ByteArrayClassLoader.SynchronizationStrategy.ForJava8CapableVm</span></td><td><code>21c8e27cd4c15a86</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassFilePostProcessor.NoOp</span></td><td><code>6146ad1c41c815ee</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassInjector.AbstractBase</span></td><td><code>ce000c0dddee09b8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassInjector.UsingReflection</span></td><td><code>60d49caaef8c73f3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassInjector.UsingReflection.Dispatcher.CreationAction</span></td><td><code>ee0e94bc96dd6d6e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassInjector.UsingReflection.Dispatcher.UsingUnsafeInjection</span></td><td><code>f184e59f3b2534d0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassInjector.UsingUnsafe</span></td><td><code>abadc114ff2762ea</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassInjector.UsingUnsafe.Dispatcher.CreationAction</span></td><td><code>e5ff6320adefa895</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassInjector.UsingUnsafe.Dispatcher.Enabled</span></td><td><code>90e152f5fb4a1efd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassLoadingStrategy</span></td><td><code>eee19f05d61ced57</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default</span></td><td><code>ae5c3ef7ed856529</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default.InjectionDispatcher</span></td><td><code>627e6e90dd12f432</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassLoadingStrategy.Default.WrappingDispatcher</span></td><td><code>d522fe32cdaeda74</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.ClassLoadingStrategy.ForUnsafeInjection</span></td><td><code>856e0dbae2eb5fd3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.InjectionClassLoader</span></td><td><code>41bc10f352e48896</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.PackageDefinitionStrategy.Definition.Trivial</span></td><td><code>968c1d05fe34e6ba</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.PackageDefinitionStrategy.NoOp</span></td><td><code>8adad700f8f7e940</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.loading.PackageDefinitionStrategy.Trivial</span></td><td><code>1b8be35a135f3f27</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.FieldRegistry.Default</span></td><td><code>df7258e8a7d2bccb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.FieldRegistry.Default.Compiled</span></td><td><code>21e60a06e38fcadd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.InstrumentedType.Default</span></td><td><code>427aa4db1c1fc4e1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.InstrumentedType.Factory.Default</span></td><td><code>c7f976e803f8d3a8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.InstrumentedType.Factory.Default.1</span></td><td><code>01e6e0df84609179</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.InstrumentedType.Factory.Default.2</span></td><td><code>41b308deac98792e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler</span></td><td><code>6b8d18710c99d74d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.AbstractBase</span></td><td><code>86739f96369d83b2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default</span></td><td><code>b0fe01def0e28a5e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Harmonizer.ForJavaMethod</span></td><td><code>8ef28acf242eae2d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Harmonizer.ForJavaMethod.Token</span></td><td><code>7bd97f7cecaf5018</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Key</span></td><td><code>88f2458db8c741e7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Key.Detached</span></td><td><code>89750a8204bacfc3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Key.Harmonized</span></td><td><code>a31e139e57804f56</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Key.Store</span></td><td><code>2745c6d4c83854fa</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Key.Store.Entry.Initial</span></td><td><code>a1eb87bc66f88929</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Key.Store.Entry.Resolved</span></td><td><code>8267c5ccc810eabd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Key.Store.Entry.Resolved.Node</span></td><td><code>fc2ebfd417a30be9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Key.Store.Graph</span></td><td><code>c9ca8a68b1a906e6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.Default.Merger.Directional</span></td><td><code>afc0868aacc8dbb5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Compiler.ForDeclaredMethods</span></td><td><code>6c4c0c83346af9e0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Empty</span></td><td><code>0f8190524ba952c8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Linked.Delegation</span></td><td><code>cbc5a7d0b5f1a9a3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Node.Simple</span></td><td><code>bf93d0430a3c37c9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Node.Sort</span></td><td><code>c997ba920aa81bb5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.NodeList</span></td><td><code>ddb63425295dab8e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodGraph.Simple</span></td><td><code>1a506b2f0d665aea</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodRegistry.Default</span></td><td><code>89e5111a94048c28</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodRegistry.Default.Compiled</span></td><td><code>a9278ec812159ded</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodRegistry.Default.Compiled.Entry</span></td><td><code>03416d340de766a9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodRegistry.Default.Entry</span></td><td><code>e0bfb0e723aa3689</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodRegistry.Default.Prepared</span></td><td><code>363cdc5888b2d43b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodRegistry.Default.Prepared.Entry</span></td><td><code>c86f6176fdd7bc14</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodRegistry.Handler.ForImplementation</span></td><td><code>96a42aa4e5822cb4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.MethodRegistry.Handler.ForImplementation.Compiled</span></td><td><code>1419c5aea9f41a32</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.RecordComponentRegistry.Default</span></td><td><code>bca912c2f4a003c7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.RecordComponentRegistry.Default.Compiled</span></td><td><code>54491302d12e41d8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeInitializer.Drain.Default</span></td><td><code>5ee97596d4a49e9f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeInitializer.None</span></td><td><code>0aa5903ad3f1d635</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeValidation</span></td><td><code>50527b71bc87dd74</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default</span></td><td><code>6150246d196f0887</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ClassDumpAction.Dispatcher.Disabled</span></td><td><code>3fe19f4a879aa9d4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForCreation</span></td><td><code>7ccca5e90bbaacf8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining</span></td><td><code>48df592d034e715a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.ContextRegistry</span></td><td><code>30f87ac3b6e91d07</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.WithFullProcessing</span></td><td><code>db39a220192293ef</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.WithFullProcessing.InitializationHandler.Appending</span></td><td><code>78681a98b1676574</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.WithFullProcessing.InitializationHandler.Appending.FrameWriter.NoOp</span></td><td><code>4e676b84c2db9221</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.WithFullProcessing.InitializationHandler.Appending.WithoutDrain</span></td><td><code>1c35fc86761df78a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.WithFullProcessing.InitializationHandler.Appending.WithoutDrain.WithoutActiveRecord</span></td><td><code>a9601fe4f1c04d81</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.WithFullProcessing.InitializationHandler.Creating</span></td><td><code>1863bc3b7943bb2b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.WithFullProcessing.OpenedClassRemapper</span></td><td><code>05e274c0bc9d98a8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ForInlining.WithFullProcessing.RedefinitionClassVisitor</span></td><td><code>7e92d38df1f2960f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.SignatureKey</span></td><td><code>30f02c677c168ffb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.UnresolvedType</span></td><td><code>12e9ea42af9d9adf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ValidatingClassVisitor</span></td><td><code>fef9b21649f006c2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ValidatingClassVisitor.Constraint.Compound</span></td><td><code>b90cfd5ddce7337a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ValidatingClassVisitor.Constraint.ForClass</span></td><td><code>d6e8080c2ac49ca3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ValidatingClassVisitor.Constraint.ForClassFileVersion</span></td><td><code>ce27ab0b452d8367</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ValidatingClassVisitor.ValidatingFieldVisitor</span></td><td><code>5fafc403ffa7635b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.Default.ValidatingClassVisitor.ValidatingMethodVisitor</span></td><td><code>0f3d60cd71ee55ec</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.FieldPool.Record.ForImplicitField</span></td><td><code>45aac26b91ec5dda</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool.Record.AccessBridgeWrapper</span></td><td><code>fb85d5b22513f8b1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool.Record.ForDefinedMethod</span></td><td><code>f6b5f34a9b4e8f93</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody</span></td><td><code>aa425dc672e34cc8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool.Record.ForNonImplementedMethod</span></td><td><code>93181b9028ecf38e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.TypeWriter.MethodPool.Record.Sort</span></td><td><code>9c1fc9a17d6e668e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.inline.AbstractInliningDynamicTypeBuilder</span></td><td><code>8579270eaa131a5e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.inline.InliningImplementationMatcher</span></td><td><code>dc5814aa75342e21</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.inline.MethodRebaseResolver.Disabled</span></td><td><code>7fc8122bae557f2d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.inline.RedefinitionDynamicTypeBuilder</span></td><td><code>d35704c254c6bf65</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default</span></td><td><code>30f93c3d785de262</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default.1</span></td><td><code>c55a41a20be13da8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default.2</span></td><td><code>1b59827354f09dbe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default.3</span></td><td><code>03f49005d185f31e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default.4</span></td><td><code>8abb20a81cdfe753</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy.Default.5</span></td><td><code>03da9e9a4baa343b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder</span></td><td><code>8345d8596b58fb1f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.SubclassDynamicTypeBuilder.InstrumentableMatcher</span></td><td><code>84afae1ae935c04f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget</span></td><td><code>90f50c9737b5f568</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget.Factory</span></td><td><code>9507db5376136dc4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget.OriginTypeResolver</span></td><td><code>e3591b410f1beb48</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget.OriginTypeResolver.1</span></td><td><code>307c2f6381acad86</code></td></tr><tr><td><span class="el_class">net.bytebuddy.dynamic.scaffold.subclass.SubclassImplementationTarget.OriginTypeResolver.2</span></td><td><code>d07ed98e74e119c8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.Default</span></td><td><code>a4f77d0e8c30d8ba</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.Default.Factory</span></td><td><code>7907cd1700b68712</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.Disabled</span></td><td><code>d226525d9b1661c1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.Disabled.Factory</span></td><td><code>fc5147fce792870c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.ExtractableView.AbstractBase</span></td><td><code>54fb2a8b0224c709</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.FrameGeneration</span></td><td><code>732fca6ba6d7f948</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.FrameGeneration.1</span></td><td><code>e1cb50e88c828853</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.FrameGeneration.2</span></td><td><code>eced4d043d2746ae</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Context.FrameGeneration.3</span></td><td><code>896ff9ab8ad2a703</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.SpecialMethodInvocation.AbstractBase</span></td><td><code>617a756fb7ad1ead</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.SpecialMethodInvocation.Simple</span></td><td><code>3b1d460fdb691665</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Target.AbstractBase</span></td><td><code>60f415a30aa4f263</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Target.AbstractBase.DefaultMethodInvocation</span></td><td><code>95cadf8bb1c40a5e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Target.AbstractBase.DefaultMethodInvocation.1</span></td><td><code>1522748b6ede90f9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.Implementation.Target.AbstractBase.DefaultMethodInvocation.2</span></td><td><code>1a1e99939835b649</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.LoadedTypeInitializer.NoOp</span></td><td><code>079c0db350266bf1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall</span></td><td><code>4252ee3c2d260285</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.Appender</span></td><td><code>66f171e335e18684</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.ArgumentLoader.ForMethodParameter</span></td><td><code>bc823c2ce1b04e7a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.ArgumentLoader.ForMethodParameter.Factory</span></td><td><code>8e777967afd380a0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.MethodInvoker.ForContextualInvocation</span></td><td><code>40de4c092ecf7daa</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.MethodInvoker.ForContextualInvocation.Factory</span></td><td><code>8e991c8f6c61a26b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.MethodInvoker.ForVirtualInvocation.WithImplicitType</span></td><td><code>4b9ee3e1372d7a3c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.MethodLocator.ForExplicitMethod</span></td><td><code>4cdbdcdca467ee30</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TargetHandler.ForMethodCall</span></td><td><code>18cad6ce289c7409</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TargetHandler.ForMethodCall.Factory</span></td><td><code>67320825359078b3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TargetHandler.ForMethodCall.Resolved</span></td><td><code>94ff85dedc76beed</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TargetHandler.ForMethodParameter</span></td><td><code>06e042a67910fcd7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TargetHandler.ForMethodParameter.Resolved</span></td><td><code>acb849d70e29f54e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TargetHandler.ForSelfOrStaticInvocation</span></td><td><code>67d84d87b377a3ec</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TargetHandler.ForSelfOrStaticInvocation.Factory</span></td><td><code>f95bda15a3f3fc2d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TargetHandler.ForSelfOrStaticInvocation.Resolved</span></td><td><code>6907aa5f1a1639e9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TerminationHandler.Simple</span></td><td><code>94548bcde51ac7c5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TerminationHandler.Simple.1</span></td><td><code>51a0379b030a2561</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TerminationHandler.Simple.2</span></td><td><code>0f85a678e3b7ce29</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.TerminationHandler.Simple.3</span></td><td><code>75c93aba20f438fe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodCall.WithoutSpecifiedTarget</span></td><td><code>de94872b451b3f74</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodDelegation</span></td><td><code>8e165dc3bcc0702b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodDelegation.ImplementationDelegate.ForStaticMethod</span></td><td><code>2c4a6692ffc55513</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.MethodDelegation.WithCustomProperties</span></td><td><code>bc9c8851e6f4ee64</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.SuperMethodCall</span></td><td><code>611355edd9b41de2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.SuperMethodCall.Appender</span></td><td><code>e101bc60914b9e07</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.SuperMethodCall.Appender.TerminationHandler</span></td><td><code>32d5e0e8182a3358</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.SuperMethodCall.Appender.TerminationHandler.1</span></td><td><code>0262af83a3a8b2c1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.SuperMethodCall.Appender.TerminationHandler.2</span></td><td><code>ee7147093f933eea</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationAppender.Default</span></td><td><code>b0bb851a926198a3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationAppender.ForTypeAnnotations</span></td><td><code>0af0d315837867ea</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationAppender.Target.OnMethod</span></td><td><code>19a7c61eaa942644</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationAppender.Target.OnMethodParameter</span></td><td><code>370e4a9b6a98e95a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationAppender.Target.OnType</span></td><td><code>aafb4a63e88bf377</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationRetention</span></td><td><code>da4f57f00f2339fe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationValueFilter.Default</span></td><td><code>c3f974ecaffb54e9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationValueFilter.Default.1</span></td><td><code>16794e96c48a9eb1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.AnnotationValueFilter.Default.2</span></td><td><code>e61de6f9507d0593</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.MethodAttributeAppender.ForInstrumentedMethod</span></td><td><code>a824f27ba759e23b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.MethodAttributeAppender.ForInstrumentedMethod.1</span></td><td><code>496e9baf72f72b4d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.MethodAttributeAppender.ForInstrumentedMethod.2</span></td><td><code>96f514167b7d0a48</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.MethodAttributeAppender.NoOp</span></td><td><code>b52b89cf16c54ff9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.TypeAttributeAppender.ForInstrumentedType</span></td><td><code>174b3e561dfe2a50</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.attribute.TypeAttributeAppender.ForInstrumentedType.Differentiating</span></td><td><code>d87f0d025f63d1bc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.auxiliary.AuxiliaryType.NamingStrategy.SuffixingRandom</span></td><td><code>4536d22ec00d9bb4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.ArgumentTypeResolver</span></td><td><code>11f8505cde4d19ff</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.DeclaringTypeResolver</span></td><td><code>dc04124901f1b333</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver</span></td><td><code>36f5bfa2a412ee72</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.MethodDelegationBinder.AmbiguityResolver.Compound</span></td><td><code>dc8def3d1db8bc32</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.MethodDelegationBinder.BindingResolver.Default</span></td><td><code>c33dc6b307529852</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.MethodDelegationBinder.TerminationHandler.Default</span></td><td><code>c7d75b957a2f27ed</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.MethodDelegationBinder.TerminationHandler.Default.1</span></td><td><code>4ec0d44d8c3eceb0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.MethodDelegationBinder.TerminationHandler.Default.2</span></td><td><code>1ad724fd05690ddc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.MethodNameEqualityResolver</span></td><td><code>0c639151a5005f3c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.ParameterLengthResolver</span></td><td><code>f4653916b8323a8e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.AllArguments.Assignment</span></td><td><code>5ff910d53a6f2d0f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.AllArguments.Binder</span></td><td><code>758af7bb09f652cd</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.Argument.Binder</span></td><td><code>dc92b4735795f877</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.Argument.BindingMechanic</span></td><td><code>99796f5e37a26565</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.Argument.BindingMechanic.1</span></td><td><code>367024ef2f1388a2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.Argument.BindingMechanic.2</span></td><td><code>38ba3faff880e1c9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.BindingPriority.Resolver</span></td><td><code>04a1eb2c2d2ecfac</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.Default.Binder</span></td><td><code>3d4b610c3e192abc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.DefaultCall.Binder</span></td><td><code>d05b63f0a6321461</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.DefaultCallHandle.Binder</span></td><td><code>13bdeb27076fd371</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.DefaultMethod.Binder</span></td><td><code>e4e08f789ce159e2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.DefaultMethodHandle.Binder</span></td><td><code>190a13afd14a1a6d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.Empty.Binder</span></td><td><code>d3025af78526455a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.FieldGetterHandle.Binder</span></td><td><code>17342fbbde9d2477</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.FieldGetterHandle.Binder.Delegate</span></td><td><code>92ca8f7c9f3a40e2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.FieldSetterHandle.Binder</span></td><td><code>e2bdded87bfab797</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.FieldSetterHandle.Binder.Delegate</span></td><td><code>2e2db5fe8cbd8444</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.FieldValue.Binder</span></td><td><code>4f15d3f13eccee26</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.FieldValue.Binder.Delegate</span></td><td><code>b9cd319375aeba70</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.IgnoreForBinding.Verifier</span></td><td><code>b6bbe4a67f2ce769</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.Origin.Binder</span></td><td><code>63d345bc80320364</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.RuntimeType.Verifier</span></td><td><code>0eda7ebdfbc4de97</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.StubValue.Binder</span></td><td><code>0fcc840e92bf2eff</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.Super.Binder</span></td><td><code>f816bc17a41fc240</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.SuperCall.Binder</span></td><td><code>e75122d32ab041df</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.SuperCallHandle.Binder</span></td><td><code>be3a9057258fb61c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.SuperMethod.Binder</span></td><td><code>acdd28af315c12f1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.SuperMethodHandle.Binder</span></td><td><code>7db979dc52744f1f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder</span></td><td><code>c6cc62a6774813df</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.DelegationProcessor</span></td><td><code>5b56b097b1796225</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.DelegationProcessor.Handler.Bound</span></td><td><code>996ae47001ba94e6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.DelegationProcessor.Handler.Unbound</span></td><td><code>c887ac626f3f7a5a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder</span></td><td><code>f2dafd3aacd92fa8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFieldBinding</span></td><td><code>ebb42ae656fa7981</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue</span></td><td><code>58001d6737a8a353</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.ParameterBinder.ForFixedValue.OfConstant</span></td><td><code>62c01535d933852b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder.Record</span></td><td><code>240ce0524f64483f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bind.annotation.This.Binder</span></td><td><code>eaf168e8e2de8dac</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.ByteCodeAppender.Size</span></td><td><code>c02d58dc06fa0040</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.Removal</span></td><td><code>20e0c2619ab9e596</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.Removal.1</span></td><td><code>ec81593288755b57</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.Removal.2</span></td><td><code>7bafc790d8ad6b0a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.StackManipulation.AbstractBase</span></td><td><code>31ada1cf9b3e1f09</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.StackManipulation.Compound</span></td><td><code>72bb1ddc292560e4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.StackManipulation.Illegal</span></td><td><code>d208c868604ff6a8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.StackManipulation.Size</span></td><td><code>1862aefaf0b5d58b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.StackManipulation.Trivial</span></td><td><code>704241e2b0e40c6e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.StackSize</span></td><td><code>4336788f1a965d2e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.StackSize.1</span></td><td><code>96b2506c3d21bf91</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.Assigner</span></td><td><code>a12889e04d303449</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.Assigner.Typing</span></td><td><code>5388b1bfde68c6c0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.TypeCasting</span></td><td><code>0f8900f5d39422ac</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveBoxingDelegate</span></td><td><code>2247e86fa6ac6dbc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveBoxingDelegate.BoxingStackManipulation</span></td><td><code>b1150ae5e800b606</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveTypeAwareAssigner</span></td><td><code>695288e9ca391e11</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate</span></td><td><code>b288a1833178dbcf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate.ImplicitlyTypedUnboxingResponsible</span></td><td><code>ec69e69852cefd67</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveWideningDelegate</span></td><td><code>15d4895de92b4326</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveWideningDelegate.WideningStackManipulation</span></td><td><code>9a8eca1ba9b1dd5c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.primitive.VoidAwareAssigner</span></td><td><code>c8df24db5d390ced</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.reference.GenericTypeAwareAssigner</span></td><td><code>b45b3ebe7424172e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAwareAssigner</span></td><td><code>7928b92c7844ad95</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.collection.ArrayFactory</span></td><td><code>8d32ba3ed3786526</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.collection.ArrayFactory.ArrayCreator</span></td><td><code>55f358a6fd1fba94</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.collection.ArrayFactory.ArrayCreator.ForReferenceType</span></td><td><code>08d5eea5a39cb614</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.collection.ArrayFactory.ArrayStackManipulation</span></td><td><code>c373b9691f65fd95</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.ClassConstant</span></td><td><code>84d3d231c511a9e2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.ClassConstant.ForReferenceType</span></td><td><code>de50499ce49feb20</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.DefaultValue</span></td><td><code>e6636f8b6803b575</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.DoubleConstant</span></td><td><code>4605c2533c4f5ada</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.FloatConstant</span></td><td><code>a56d418e26b00881</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.IntegerConstant</span></td><td><code>cc44c84f8b41799b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.LongConstant</span></td><td><code>472b65a54ff6a910</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.MethodConstant</span></td><td><code>2af3bf9709ff88bc</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.MethodConstant.ForMethod</span></td><td><code>6487c36db906419f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.NullConstant</span></td><td><code>ce7ac6225f44f48a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.constant.TextConstant</span></td><td><code>96afe89e29325099</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.member.MethodInvocation</span></td><td><code>04585fe19a2e6b09</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.member.MethodInvocation.Invocation</span></td><td><code>34b362dfec431553</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.member.MethodReturn</span></td><td><code>d6f400afc0cab59b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.member.MethodVariableAccess</span></td><td><code>ee51af3004f9e32e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.member.MethodVariableAccess.MethodLoading</span></td><td><code>5b576f1cea89665e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.member.MethodVariableAccess.MethodLoading.TypeCastingHandler.NoOp</span></td><td><code>fce3bb47777272e1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.member.MethodVariableAccess.OffsetLoading</span></td><td><code>5f60efc1d05df69c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.implementation.bytecode.member.MethodVariableAccess.OffsetWriting</span></td><td><code>5917ecf4443cbdd0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.AnnotationVisitor</span></td><td><code>823759e238bb495b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.AnnotationWriter</span></td><td><code>59e14608f0f0fc16</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.Attribute</span></td><td><code>e6480519ef45eaba</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.ByteVector</span></td><td><code>29f90958ccc2d657</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.ClassReader</span></td><td><code>47c05b0e48f2aa96</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.ClassVisitor</span></td><td><code>1753f680b0943b55</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.ClassWriter</span></td><td><code>9aacb0d7c169551b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.ConstantDynamic</span></td><td><code>2d2f6ed798e1e63f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.Context</span></td><td><code>a881f26b77892c9d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.FieldVisitor</span></td><td><code>2fefa241e92a2948</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.FieldWriter</span></td><td><code>7b8af1d3e89c08fe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.Handle</span></td><td><code>f00028b1416f621c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.Handler</span></td><td><code>357ee9a4f87e5091</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.Label</span></td><td><code>c329ef00234aa4ba</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.MethodVisitor</span></td><td><code>91abf2ef44da98a7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.MethodWriter</span></td><td><code>f98aa1935839115a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.Opcodes</span></td><td><code>e6269b3d7a10a281</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.Symbol</span></td><td><code>09ab9f266ba03e77</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.SymbolTable</span></td><td><code>4901893015116b81</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.SymbolTable.Entry</span></td><td><code>f06a931baef45238</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.Type</span></td><td><code>76fc57d12696f74f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.TypeReference</span></td><td><code>0bfc56de38a7304f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.commons.ClassRemapper</span></td><td><code>fa2282ed529f3c8b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.commons.FieldRemapper</span></td><td><code>b703fecc9c145e28</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.commons.Remapper</span></td><td><code>1c6aebe9dc0a4c81</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.commons.SignatureRemapper</span></td><td><code>10c3e8d1aa869a7b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.commons.SimpleRemapper</span></td><td><code>7bebbd3ab9c372ca</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.signature.SignatureReader</span></td><td><code>8bb633f6ab9cfd83</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.signature.SignatureVisitor</span></td><td><code>2a359c79b449cd9d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.jar.asm.signature.SignatureWriter</span></td><td><code>628941f852b053ca</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.AnnotationTypeMatcher</span></td><td><code>8c39c44c2fd08fcf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.BooleanMatcher</span></td><td><code>db6de6dd0dbd9032</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.CollectionErasureMatcher</span></td><td><code>6f018fafd36e6a3b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.CollectionItemMatcher</span></td><td><code>07dbf9e424f558f2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.CollectionOneToOneMatcher</span></td><td><code>1a14485fb1b595eb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.CollectionSizeMatcher</span></td><td><code>5741bfaac9283569</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.DeclaringAnnotationMatcher</span></td><td><code>2f7a69ff6c7c0cce</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.DeclaringTypeMatcher</span></td><td><code>6c2c8385a72ca883</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.DescriptorMatcher</span></td><td><code>81386cf61fba3864</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.ElementMatcher.Junction.AbstractBase</span></td><td><code>4c87a64de13c8fb6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.ElementMatcher.Junction.Conjunction</span></td><td><code>96fd08a584b73dd0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.ElementMatcher.Junction.Disjunction</span></td><td><code>2e51b7dbcf313a67</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.ElementMatcher.Junction.ForNonNullValues</span></td><td><code>405ce32bff34975f</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.ElementMatchers</span></td><td><code>229439fc61b9d724</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.EqualityMatcher</span></td><td><code>84d887d8e5a1c04a</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.ErasureMatcher</span></td><td><code>03d1734c06ee42e2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.FailSafeMatcher</span></td><td><code>6e8a1664edd4a765</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.FilterableList.AbstractBase</span></td><td><code>cb407e29a62800ed</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.FilterableList.Empty</span></td><td><code>008aecb1de0a03fe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.LatentMatcher.ForMethodToken</span></td><td><code>fe9ba0457839b368</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.LatentMatcher.ForMethodToken.ResolvedMatcher</span></td><td><code>b7c8c91ce5485a04</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.LatentMatcher.Resolved</span></td><td><code>521ac249a4bc8fec</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodParameterTypeMatcher</span></td><td><code>86d6c7e0f1df68f9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodParameterTypesMatcher</span></td><td><code>ed301f50f92df77d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodParametersMatcher</span></td><td><code>01575fd270d5c7e6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodReturnTypeMatcher</span></td><td><code>13f232c79321a43e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodSortMatcher</span></td><td><code>fbf90b4eb58a5c63</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodSortMatcher.Sort</span></td><td><code>87af87837374271b</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodSortMatcher.Sort.1</span></td><td><code>04a56e4f4f82d5c0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodSortMatcher.Sort.2</span></td><td><code>a13399cf408b62c0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodSortMatcher.Sort.3</span></td><td><code>6a11f7e01098dfb5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodSortMatcher.Sort.4</span></td><td><code>1e8c22b2e17c5f88</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.MethodSortMatcher.Sort.5</span></td><td><code>aae69164dd78b1e8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.ModifierMatcher</span></td><td><code>69fd05dd8dc2a6d1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.ModifierMatcher.Mode</span></td><td><code>b59c67438c4008d8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.NameMatcher</span></td><td><code>f48a2bcf34ddb9d3</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.NegatingMatcher</span></td><td><code>35b741e1a09ff6f6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.SignatureTokenMatcher</span></td><td><code>6eb711afdeb4084d</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher</span></td><td><code>ab012b8a197bda27</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode</span></td><td><code>6f8b4d3695faa058</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.1</span></td><td><code>015bef3b6828cdac</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.2</span></td><td><code>8eeeb4556b9485c0</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.3</span></td><td><code>f1c175ea33668dcb</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.4</span></td><td><code>84a59a498fb10fed</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.5</span></td><td><code>4ca9c62936b66a24</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.6</span></td><td><code>f993707f30c5fce2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.7</span></td><td><code>e0ba3c2aa14bde78</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.8</span></td><td><code>4ff86085d0aa4ddf</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.StringMatcher.Mode.9</span></td><td><code>1dcf4a6fe509c5b5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.TypeSortMatcher</span></td><td><code>a0260214b39460ce</code></td></tr><tr><td><span class="el_class">net.bytebuddy.matcher.VisibilityMatcher</span></td><td><code>604b5bc3cd488ef7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.AbstractBase</span></td><td><code>80b39c3e7a6e8cbe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.AbstractBase.Hierarchical</span></td><td><code>5368baa8c19e9a0e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.CacheProvider.NoOp</span></td><td><code>051ed4c43925f829</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.CacheProvider.Simple</span></td><td><code>cc47c6220f21e4b6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.ClassLoading</span></td><td><code>2c2592551f07eebe</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.Default</span></td><td><code>d18a5b9d3657e8c9</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.Default.ReaderMode</span></td><td><code>da689778f786f564</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.Empty</span></td><td><code>31a2f70d67aa1507</code></td></tr><tr><td><span class="el_class">net.bytebuddy.pool.TypePool.Explicit</span></td><td><code>b2f73b1fd93989b5</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.AsmClassReader.Default</span></td><td><code>460aaede89652510</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.AsmClassReader.Factory.Default</span></td><td><code>113a601c8c5adf46</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.AsmClassWriter.Default</span></td><td><code>a16f8321ca415fef</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.AsmClassWriter.Factory.Default</span></td><td><code>0d4373d23bbc8787</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.AsmClassWriter.FrameComputingClassWriter</span></td><td><code>fea7b3ccf26b94d4</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.CompoundList</span></td><td><code>41fe1faec9b96005</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.ConstantValue.Simple</span></td><td><code>8270fde89e22fbd7</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.ConstructorComparator</span></td><td><code>3566d64bbe3006a2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.FieldComparator</span></td><td><code>5dff49d0b60b3a07</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.GraalImageCode</span></td><td><code>e57953671f70ec71</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.Invoker.Dispatcher</span></td><td><code>b9b5f67cf01bb049</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.JavaConstant.Simple</span></td><td><code>2b4b26c6772bfaff</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.JavaConstant.Simple.OfTrivialValue</span></td><td><code>d0652e204007a782</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.JavaConstant.Simple.OfTrivialValue.ForString</span></td><td><code>059bbfdbd942e578</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.JavaModule</span></td><td><code>b9c6a19f8fb165ec</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.MethodComparator</span></td><td><code>2a643ad1fb57d7e8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.OpenedClassReader</span></td><td><code>7e1a390cbc184522</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.RandomString</span></td><td><code>a0583349bb66a97c</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.StreamDrainer</span></td><td><code>f460695d84a1b679</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher</span></td><td><code>aece09c82b616fd1</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.Dispatcher.ForContainerCreation</span></td><td><code>1f937abf15b4e1e2</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.Dispatcher.ForInstanceCheck</span></td><td><code>eb9844e76adc6c75</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.Dispatcher.ForNonStaticMethod</span></td><td><code>ec04e7a241310537</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.Dispatcher.ForStaticMethod</span></td><td><code>a004e746c713df6e</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.DynamicClassLoader</span></td><td><code>e55e160d8d2ebd92</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.DynamicClassLoader.Resolver.CreationAction</span></td><td><code>862b139a62f264a8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.DynamicClassLoader.Resolver.ForModuleSystem</span></td><td><code>4d90e44380c89f09</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.InvokerCreationAction</span></td><td><code>feae5a90b1237245</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.dispatcher.JavaDispatcher.ProxiedInvocationHandler</span></td><td><code>4c9be5e84d847d62</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.privilege.GetMethodAction</span></td><td><code>648b3ccb9f9f08d8</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.privilege.GetSystemPropertyAction</span></td><td><code>066505e53e159404</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor</span></td><td><code>a6c239974eefe586</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor</span></td><td><code>56aead9b55055305</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.visitor.MetadataAwareClassVisitor</span></td><td><code>0c557b09efdf9fd6</code></td></tr><tr><td><span class="el_class">net.bytebuddy.utility.visitor.StackAwareMethodVisitor</span></td><td><code>d92c39754d7dcde1</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.FileUtils</span></td><td><code>401274f4f5bfa1f3</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.IOExceptionList</span></td><td><code>42426ff98c14ed7c</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.file.Counters</span></td><td><code>3347b40442150325</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.file.Counters.AbstractPathCounters</span></td><td><code>f029129367b66185</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.file.Counters.LongCounter</span></td><td><code>8a0e15c3b7aab6f0</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.file.Counters.LongPathCounters</span></td><td><code>4d05a3ec2389a891</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.file.PathUtils</span></td><td><code>85fc8e518817662b</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.file.StandardDeleteOption</span></td><td><code>7b25e925ed7bca8c</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.function.IOBaseStreamAdapter</span></td><td><code>0ffcc9384f373279</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.function.IOConsumer</span></td><td><code>b1565b11a4b72fde</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.function.IOStream</span></td><td><code>b682989b12b2d8e0</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.function.IOStreamAdapter</span></td><td><code>bcbb3ff050f62f49</code></td></tr><tr><td><span class="el_class">org.apache.commons.io.function.IOStreams</span></td><td><code>eff27b481839d133</code></td></tr><tr><td><span class="el_class">org.apache.commons.lang.StringUtils</span></td><td><code>9dd94cc65aafa7e1</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.TableBlock</span></td><td><code>2d497e5352a89390</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.TableBody</span></td><td><code>c6800613e289ec20</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.TableCell</span></td><td><code>2dfa3c31778ff059</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.TableHead</span></td><td><code>b3c9471f8fa449b4</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.TableRow</span></td><td><code>a939bff33e98beea</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.TablesExtension</span></td><td><code>5ad9f10f7b42c7a1</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.TablesExtension.1</span></td><td><code>cb505c205e0200ee</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.internal.TableBlockParser</span></td><td><code>b4ad1a259a4851e7</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.internal.TableBlockParser.Factory</span></td><td><code>df0161fd06033e0b</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.internal.TableBlockParser.TableCellInfo</span></td><td><code>84bc9a5b0d6f971e</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.internal.TableHtmlNodeRenderer</span></td><td><code>16fd0e18f58c5907</code></td></tr><tr><td><span class="el_class">org.commonmark.ext.gfm.tables.internal.TableNodeRenderer</span></td><td><code>7ef9a6986503da0a</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.BlockContinueImpl</span></td><td><code>521c469dfc763010</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.BlockQuoteParser</span></td><td><code>499db8932090c7be</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.BlockQuoteParser.Factory</span></td><td><code>ce3a6c380d00a67c</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.BlockStartImpl</span></td><td><code>0bd58586dff0f0c8</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.Bracket</span></td><td><code>7ed94f88f9a9ac30</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.Definitions</span></td><td><code>9da8f4fa5983fdf0</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.Delimiter</span></td><td><code>36b2e49b3c2f31c6</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.DocumentBlockParser</span></td><td><code>39f7248655b16f80</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.DocumentParser</span></td><td><code>5e8a97a3ced165a9</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.DocumentParser.MatchedBlockParserImpl</span></td><td><code>5933cb92e6de0d21</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.DocumentParser.OpenBlockParser</span></td><td><code>699047ca7e04b5ca</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.FencedCodeBlockParser</span></td><td><code>8bb2d6325b0846c8</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.FencedCodeBlockParser.Factory</span></td><td><code>6e4d9bbb933ce202</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.HeadingParser</span></td><td><code>8fb6ce991aa160f8</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.HeadingParser.Factory</span></td><td><code>3649089f20ea5f74</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.HtmlBlockParser.Factory</span></td><td><code>26f0980df20bef5a</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.IndentedCodeBlockParser.Factory</span></td><td><code>2bdd469954e7564f</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.InlineParserContextImpl</span></td><td><code>b1a8fec95a86b5d4</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.InlineParserImpl</span></td><td><code>b35ceab4300d5348</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.InlineParserImpl.1</span></td><td><code>ab71205cdd7b4de1</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.InlineParserImpl.DelimiterData</span></td><td><code>639ba6f87ef4088c</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.InlineParserImpl.DestinationTitle</span></td><td><code>e839c0661afd6218</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.InlineParserImpl.LinkInfoImpl</span></td><td><code>1c7bb29a6a0e276c</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.LinkReferenceDefinitionParser</span></td><td><code>366b492bae43417a</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.LinkReferenceDefinitionParser.1</span></td><td><code>f3592feaef0ed5a9</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.LinkReferenceDefinitionParser.State</span></td><td><code>d13d311d302166c9</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.ListBlockParser</span></td><td><code>7e4935182a5595aa</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.ListBlockParser.Factory</span></td><td><code>8a025a36d69f88cf</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.ListBlockParser.ListData</span></td><td><code>b6fdd8cd7450ad3a</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.ListBlockParser.ListMarkerData</span></td><td><code>ea1fee557a6664ab</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.ListItemParser</span></td><td><code>3f812387eacd7099</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.ParagraphParser</span></td><td><code>ae6499f11a5ff620</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.ThematicBreakParser</span></td><td><code>933018cf0ea88a1e</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.ThematicBreakParser.Factory</span></td><td><code>94ae385bd4c7f34e</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.AsteriskDelimiterProcessor</span></td><td><code>0efbdb193cff156d</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.AutolinkInlineParser</span></td><td><code>c56690a55975808e</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.AutolinkInlineParser.Factory</span></td><td><code>b9e2bcb28227b337</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.BackslashInlineParser</span></td><td><code>ccddbd686e34f33f</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.BackslashInlineParser.Factory</span></td><td><code>0bb5fe12123c560a</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.BackticksInlineParser</span></td><td><code>ad4ed63d9d972a05</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.BackticksInlineParser.Factory</span></td><td><code>ab54a787776f03ab</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.CoreLinkProcessor</span></td><td><code>a7234da96aee88ae</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.EmphasisDelimiterProcessor</span></td><td><code>efb622e271133741</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.EntityInlineParser</span></td><td><code>6d3af9d28540f8bb</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.EntityInlineParser.Factory</span></td><td><code>c8bc7411204410d4</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.HtmlInlineParser</span></td><td><code>e651d67e0feb8083</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.HtmlInlineParser.Factory</span></td><td><code>e45ea05dd322b832</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.LinkResultImpl</span></td><td><code>f76b4fbe03c7f957</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.LinkResultImpl.Type</span></td><td><code>16462afa9ec5a1f8</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.inline.UnderscoreDelimiterProcessor</span></td><td><code>8d2f2b4a34d22e1c</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.renderer.NodeRendererMap</span></td><td><code>f2c100865a196ac0</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.util.Escaping</span></td><td><code>2fd97919b8ff07a7</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.util.Escaping.1</span></td><td><code>115a6cda4077e982</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.util.Escaping.2</span></td><td><code>2b78261c9520f896</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.util.LinkScanner</span></td><td><code>7e7f0e6a7bab75f9</code></td></tr><tr><td><span class="el_class">org.commonmark.internal.util.Parsing</span></td><td><code>50966670ef238476</code></td></tr><tr><td><span class="el_class">org.commonmark.node.AbstractVisitor</span></td><td><code>d6e3fe46a68d1a6d</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Block</span></td><td><code>4fc79dcd1ab2046b</code></td></tr><tr><td><span class="el_class">org.commonmark.node.BulletList</span></td><td><code>9dec2e1c732edd67</code></td></tr><tr><td><span class="el_class">org.commonmark.node.CustomBlock</span></td><td><code>e0941708cd4f4892</code></td></tr><tr><td><span class="el_class">org.commonmark.node.CustomNode</span></td><td><code>a7dcedd5f3b5e0c3</code></td></tr><tr><td><span class="el_class">org.commonmark.node.DefinitionMap</span></td><td><code>56d450f5e1bf4c42</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Document</span></td><td><code>28c7cc32f37115fe</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Heading</span></td><td><code>61568e56d1d80738</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Image</span></td><td><code>c6bd437ef78c6dc3</code></td></tr><tr><td><span class="el_class">org.commonmark.node.ListBlock</span></td><td><code>235bf9ef1d74ddad</code></td></tr><tr><td><span class="el_class">org.commonmark.node.ListItem</span></td><td><code>9e43dc1e949eb97e</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Node</span></td><td><code>db718e03eee739a1</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Nodes</span></td><td><code>e311c883024283b8</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Nodes.NodeIterable</span></td><td><code>275b6ba25b24ee4b</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Nodes.NodeIterator</span></td><td><code>671d486b7e4c6130</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Paragraph</span></td><td><code>c761b4a1c1941721</code></td></tr><tr><td><span class="el_class">org.commonmark.node.SourceSpans</span></td><td><code>f126a90f77fbe89f</code></td></tr><tr><td><span class="el_class">org.commonmark.node.StrongEmphasis</span></td><td><code>42eb034841475434</code></td></tr><tr><td><span class="el_class">org.commonmark.node.Text</span></td><td><code>4bc23624501e2aa9</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.IncludeSourceSpans</span></td><td><code>0278f7c3b0b0202e</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.Parser</span></td><td><code>370a7c5057ec3c18</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.Parser.Builder</span></td><td><code>70f2c84b0cadec8c</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.SourceLine</span></td><td><code>58f685257023a3c4</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.SourceLines</span></td><td><code>ee04e87d8ba87a90</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.beta.LinkResult</span></td><td><code>672d883603277cd7</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.beta.Position</span></td><td><code>39d1c652aa026d55</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.beta.Scanner</span></td><td><code>dbc028cabdabf2b7</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.block.AbstractBlockParser</span></td><td><code>74330b8bf093de19</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.block.AbstractBlockParserFactory</span></td><td><code>9661a5f712efb3fa</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.block.BlockContinue</span></td><td><code>76052bdc2d4e344d</code></td></tr><tr><td><span class="el_class">org.commonmark.parser.block.BlockStart</span></td><td><code>832a53c9eb098422</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.NodeRenderer</span></td><td><code>ba7ec82a172f1c6d</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.html.CoreHtmlNodeRenderer</span></td><td><code>61c02d9cec49a8b8</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.html.CoreHtmlNodeRenderer.AltTextVisitor</span></td><td><code>9da5a85ad1126514</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.html.DefaultUrlSanitizer</span></td><td><code>e83ca62f85d58881</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.html.HtmlRenderer</span></td><td><code>4025b82b2f233618</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.html.HtmlRenderer.1</span></td><td><code>39e10d0aebdf214a</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.html.HtmlRenderer.Builder</span></td><td><code>93a5bc05a22d5102</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.html.HtmlRenderer.RendererContext</span></td><td><code>53b329574bf67f23</code></td></tr><tr><td><span class="el_class">org.commonmark.renderer.html.HtmlWriter</span></td><td><code>77af2591b9872411</code></td></tr><tr><td><span class="el_class">org.commonmark.text.AsciiMatcher</span></td><td><code>a4b9c925424cd536</code></td></tr><tr><td><span class="el_class">org.commonmark.text.AsciiMatcher.Builder</span></td><td><code>cfdc579066b882b4</code></td></tr><tr><td><span class="el_class">org.commonmark.text.Characters</span></td><td><code>3c26222ab25d17c0</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.AbstractTestDescriptor</span></td><td><code>b7d6764e5c2ed1e2</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.DefaultTestClassDescriptor</span></td><td><code>29a580f844a707e9</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.DefaultTestClassRunInfo</span></td><td><code>68a7e79b2914fd4d</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.DefaultTestDescriptor</span></td><td><code>62e300564099c798</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.DefaultTestOutputEvent</span></td><td><code>3d7991cb0119492d</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.DefaultTestSuiteDescriptor</span></td><td><code>7ca2225e2fb0b4b2</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor</span></td><td><code>af72bb8826a6828d</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.TestCompleteEvent</span></td><td><code>94a6da85674017e0</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.TestStartEvent</span></td><td><code>739a2bff9c36ddab</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.failure.DefaultThrowableToTestFailureMapper</span></td><td><code>98b3c6d95620e628</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.failure.TestFailureMapper</span></td><td><code>fad0361b08728e0d</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.failure.mappers.AssertErrorMapper</span></td><td><code>f8f52c2b08659a75</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.failure.mappers.AssertjMultipleAssertionsErrorMapper</span></td><td><code>6d9c88eceee97e47</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.failure.mappers.JUnitComparisonTestFailureMapper</span></td><td><code>1870ccedd70c62d3</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.failure.mappers.OpenTestAssertionFailedMapper</span></td><td><code>670c8d72a39d27d6</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.failure.mappers.OpenTestMultipleFailuresErrorMapper</span></td><td><code>fd9e46b889182617</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.filter.TestFilterSpec</span></td><td><code>a7526e6ebab295a4</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor</span></td><td><code>e052047f692cb949</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformSpec</span></td><td><code>f5579f12caeb524a</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor</span></td><td><code>4c75223a5693afa4</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.BackwardsCompatibleLauncherSession</span></td><td><code>7c93d17cac3a9550</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.CollectAllTestClassesExecutor</span></td><td><code>4b0f383cf965c86c</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessorFactory</span></td><td><code>af09c0eae5fa5ced</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestExecutionListener</span></td><td><code>73940d825c87c199</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.processors.CaptureTestOutputTestResultProcessor</span></td><td><code>6ad5ce3fa22109c1</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.redirector.DefaultStandardOutputRedirector</span></td><td><code>66011962fbed0a65</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.redirector.DefaultStandardOutputRedirector.DiscardAction</span></td><td><code>c8ea3545fa32c9b3</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.redirector.DefaultStandardOutputRedirector.WriteAction</span></td><td><code>77c2ee9a9fa842b1</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.redirector.JULRedirector</span></td><td><code>14551eb76b8ecae2</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.redirector.TestOutputRedirector</span></td><td><code>3bd5f34889305e8d</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.redirector.TestOutputRedirector.Forwarder</span></td><td><code>d61609d95c6d50d3</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor</span></td><td><code>6d02567fd2a7d62d</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer</span></td><td><code>86c8a2a7f444af17</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultNestedTestSuiteDescriptorSerializer</span></td><td><code>405bbedb146198d2</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultParameterizedTestDescriptorSerializer</span></td><td><code>45028c554f0eb6bc</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultTestClassDescriptorSerializer</span></td><td><code>697a77740ee6945b</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultTestClassRunInfoSerializer</span></td><td><code>756d4261d461b736</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultTestDescriptorSerializer</span></td><td><code>b35a78d777c1be5c</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultTestFailureSerializer</span></td><td><code>35edd933feff8786</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultTestMethodDescriptorSerializer</span></td><td><code>97b301f48f78b780</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultTestOutputEventSerializer</span></td><td><code>a41963bba4ece672</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.DefaultTestSuiteDescriptorSerializer</span></td><td><code>b5c82c1a6bc5b688</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.IdSerializer</span></td><td><code>cdde38a9abcdd3c1</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.NullableSerializer</span></td><td><code>0b8b3e72f4fb326b</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.TestCompleteEventSerializer</span></td><td><code>971566bf8e6bbbc9</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.TestStartEventSerializer</span></td><td><code>46e7b262d38e1858</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestEventSerializer.WorkerTestSuiteDescriptorSerializer</span></td><td><code>239de812dc2258cc</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestWorker</span></td><td><code>b93df52c3074c532</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestWorker.1</span></td><td><code>b8314e777e355f7d</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestWorker.2</span></td><td><code>cc4e0ec7222faee6</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestWorker.3</span></td><td><code>030c348be1c106a4</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestWorker.State</span></td><td><code>903c35047a140ff9</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.TestWorker.TestFrameworkServiceRegistry</span></td><td><code>12a7a3db90dec4e3</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.WorkerTestClassProcessor</span></td><td><code>0f6525b02f2bb3d2</code></td></tr><tr><td><span class="el_class">org.gradle.api.internal.tasks.testing.worker.WorkerTestClassProcessor.WorkerTestSuiteDescriptor</span></td><td><code>b3807e9b92351840</code></td></tr><tr><td><span class="el_class">org.gradle.api.logging.LogLevel</span></td><td><code>236e938e30516638</code></td></tr><tr><td><span class="el_class">org.gradle.api.tasks.testing.TestOutputEvent.Destination</span></td><td><code>5f28eafb6895d752</code></td></tr><tr><td><span class="el_class">org.gradle.internal.Cast</span></td><td><code>6130c81e08d81640</code></td></tr><tr><td><span class="el_class">org.gradle.internal.MutableBoolean</span></td><td><code>349de2b8a37d4338</code></td></tr><tr><td><span class="el_class">org.gradle.internal.actor.internal.DefaultActorFactory</span></td><td><code>43acc9e5b3a492ea</code></td></tr><tr><td><span class="el_class">org.gradle.internal.actor.internal.DefaultActorFactory.BlockingActor</span></td><td><code>e2e64c7c8141667f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.AbstractDelegatingExecutorService</span></td><td><code>49d3dededcea40d0</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.AbstractManagedExecutor</span></td><td><code>3e801af2d608b388</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.AbstractManagedExecutor.1</span></td><td><code>e617b5280c0b131e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.CompositeStoppable</span></td><td><code>4a36b916ba0a65be</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.CompositeStoppable.1</span></td><td><code>7991c35593ad107a</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.CompositeStoppable.3</span></td><td><code>e70f2f4c49f9854e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.DefaultExecutorFactory</span></td><td><code>63847aa635eddd82</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.DefaultExecutorFactory.TrackedManagedExecutor</span></td><td><code>36f4bc1cd93c039c</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.ExecutorPolicy.CatchAndRecordFailures</span></td><td><code>2aacf6d3d0dd2240</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.ManagedExecutorImpl</span></td><td><code>ce6f255f6fc1de83</code></td></tr><tr><td><span class="el_class">org.gradle.internal.concurrent.ThreadFactoryImpl</span></td><td><code>1d388becbfb01ad8</code></td></tr><tr><td><span class="el_class">org.gradle.internal.dispatch.ContextClassLoaderDispatch</span></td><td><code>132d0c3fd93e8141</code></td></tr><tr><td><span class="el_class">org.gradle.internal.dispatch.ContextClassLoaderProxy</span></td><td><code>d72ee515504b89e6</code></td></tr><tr><td><span class="el_class">org.gradle.internal.dispatch.MethodInvocation</span></td><td><code>bbd5401404e52b1f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.dispatch.ProxyDispatchAdapter</span></td><td><code>67194db65692ab5d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.dispatch.ProxyDispatchAdapter.DispatchingInvocationHandler</span></td><td><code>82935bb9f2db6b85</code></td></tr><tr><td><span class="el_class">org.gradle.internal.dispatch.ReflectionDispatch</span></td><td><code>6976fdf67f3e8979</code></td></tr><tr><td><span class="el_class">org.gradle.internal.event.AbstractBroadcastDispatch</span></td><td><code>0f5ffe97fa60f855</code></td></tr><tr><td><span class="el_class">org.gradle.internal.event.BroadcastDispatch</span></td><td><code>971f57b98f410335</code></td></tr><tr><td><span class="el_class">org.gradle.internal.event.BroadcastDispatch.CompositeDispatch</span></td><td><code>5d8a079ebb58640f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.event.BroadcastDispatch.EmptyDispatch</span></td><td><code>136c24cb564bd0f5</code></td></tr><tr><td><span class="el_class">org.gradle.internal.event.BroadcastDispatch.SingletonDispatch</span></td><td><code>66b75507cc7e3700</code></td></tr><tr><td><span class="el_class">org.gradle.internal.event.ListenerBroadcast</span></td><td><code>68e3a1fc76137d54</code></td></tr><tr><td><span class="el_class">org.gradle.internal.id.CompositeIdGenerator</span></td><td><code>f1c607aa5fccdbaa</code></td></tr><tr><td><span class="el_class">org.gradle.internal.id.CompositeIdGenerator.CompositeId</span></td><td><code>e710c854f802c58b</code></td></tr><tr><td><span class="el_class">org.gradle.internal.id.LongIdGenerator</span></td><td><code>6f8168bf486a560d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.id.UUIDGenerator</span></td><td><code>047a43ab94df6ffa</code></td></tr><tr><td><span class="el_class">org.gradle.internal.io.BufferCaster</span></td><td><code>88a8af829d9f2dca</code></td></tr><tr><td><span class="el_class">org.gradle.internal.io.LineBufferingOutputStream</span></td><td><code>6219fe05ee4c9468</code></td></tr><tr><td><span class="el_class">org.gradle.internal.io.LinePerThreadBufferingOutputStream</span></td><td><code>6663ead4c1825a46</code></td></tr><tr><td><span class="el_class">org.gradle.internal.io.NullOutputStream</span></td><td><code>eefcfe0665bbfe4c</code></td></tr><tr><td><span class="el_class">org.gradle.internal.io.StreamByteBuffer</span></td><td><code>e2c8064318ed213e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.io.StreamByteBuffer.StreamByteBufferChunk</span></td><td><code>601a3b84d114befa</code></td></tr><tr><td><span class="el_class">org.gradle.internal.io.StreamByteBuffer.StreamByteBufferInputStream</span></td><td><code>1ba4f1560d8a3753</code></td></tr><tr><td><span class="el_class">org.gradle.internal.io.StreamByteBuffer.StreamByteBufferOutputStream</span></td><td><code>9243acabfced691c</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.config.LoggingSystemAdapter</span></td><td><code>2bb5150ee66232e9</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.config.LoggingSystemAdapter.SnapshotImpl</span></td><td><code>221de860d84422df</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.console.DefaultUserInputReceiver</span></td><td><code>44c8536611e071b4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.events.EndOutputEvent</span></td><td><code>0d8edd2a5ce274ee</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.events.LogLevelChangeEvent</span></td><td><code>33b762c6d5852de7</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.events.OutputEvent</span></td><td><code>85bce87f1bcda18d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.serializer.LogEventSerializer</span></td><td><code>b6d88af223db296a</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.serializer.LogLevelChangeEventSerializer</span></td><td><code>f77a59533dde75ec</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.serializer.SpanSerializer</span></td><td><code>5f773b7d1ad07c9f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.serializer.StyledTextOutputEventSerializer</span></td><td><code>faebed27ac3e65ba</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.services.DefaultLoggingManager</span></td><td><code>61e216a064052ff1</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.services.DefaultLoggingManager.StartableLoggingRouter</span></td><td><code>78396be937af48de</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.services.DefaultLoggingManager.StartableLoggingSystem</span></td><td><code>b121a97021902643</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.services.DefaultLoggingManagerFactory</span></td><td><code>eb1ab97193f0d177</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.services.LoggingServiceRegistry</span></td><td><code>b54e061596ce61fc</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.services.LoggingServiceRegistry.1</span></td><td><code>b3c4addf0f9c5893</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.services.LoggingServiceRegistry.CommandLineLogging</span></td><td><code>ab0f0f2f9e415a5d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.services.TextStreamOutputEventListener</span></td><td><code>ef4d0c3267356598</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.sink.OutputEventListenerManager</span></td><td><code>d6dee3d6fea49020</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.sink.OutputEventListenerManager.1</span></td><td><code>1e218a705ff0ee7e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.sink.OutputEventRenderer</span></td><td><code>ad1cc4fc552ab292</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.sink.OutputEventRenderer.1</span></td><td><code>b94418b2e260aede</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.sink.OutputEventRenderer.2</span></td><td><code>a956801cd98ca633</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.sink.OutputEventRenderer.LazyListener</span></td><td><code>7ee882569b166e1e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.sink.OutputEventRenderer.SnapshotImpl</span></td><td><code>827634628a47c5f5</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.sink.OutputEventTransformer</span></td><td><code>06c2270eef0e291e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.slf4j.BuildOperationAwareLogger</span></td><td><code>6a70f9123229323f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger</span></td><td><code>30ddd0a8ff91b5f5</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.slf4j.OutputEventListenerBackedLoggerContext</span></td><td><code>9d35d4b7a722eb08</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.slf4j.OutputEventListenerBackedLoggerContext.NoOpLogger</span></td><td><code>3868cc8d50014a37</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.slf4j.Slf4jLoggingConfigurer</span></td><td><code>75fba29c3739b15f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.DefaultStdErrLoggingSystem</span></td><td><code>fd3dd0caab2f1d95</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.DefaultStdOutLoggingSystem</span></td><td><code>528bb39bfb67c3ae</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.JavaUtilLoggingSystem</span></td><td><code>5e967b17aabfd442</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.JavaUtilLoggingSystem.SnapshotImpl</span></td><td><code>15dfc30250723749</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.PrintStreamLoggingSystem</span></td><td><code>1ae6e6b715c6b3f9</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.PrintStreamLoggingSystem.1</span></td><td><code>65643cb979acba64</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.PrintStreamLoggingSystem.OutputEventDestination</span></td><td><code>8c1ddf1476568828</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.PrintStreamLoggingSystem.PrintStreamDestination</span></td><td><code>9e7273f370028123</code></td></tr><tr><td><span class="el_class">org.gradle.internal.logging.source.PrintStreamLoggingSystem.SnapshotImpl</span></td><td><code>8f80a46f9780a57e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.filesystem.services.FileSystemServices</span></td><td><code>4556096f1c5a9c8e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.jansi.DefaultJansiRuntimeResolver</span></td><td><code>913dbea9c5665791</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.jansi.JansiBootPathConfigurer</span></td><td><code>3a766bce65ac1a48</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.jansi.JansiLibraryFactory</span></td><td><code>0cbaac430d6656c4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.jansi.JansiStorageLocator</span></td><td><code>c8bff1ccb071f9b6</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices</span></td><td><code>8db3622d664d053e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices.1</span></td><td><code>67c99d0a2cd1e155</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices.NativeFeatures</span></td><td><code>70de3a72c57afe4c</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices.NativeFeatures.1</span></td><td><code>38be0c558161988d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices.NativeFeatures.2</span></td><td><code>227885d00841d12e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices.NativeServicesMode</span></td><td><code>3448c60568fa6658</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices.NativeServicesMode.1</span></td><td><code>6f287a3af2ef59e4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices.NativeServicesMode.2</span></td><td><code>f2cd65a1d8088dc6</code></td></tr><tr><td><span class="el_class">org.gradle.internal.nativeintegration.services.NativeServices.NativeServicesMode.3</span></td><td><code>3e196f14d19a5211</code></td></tr><tr><td><span class="el_class">org.gradle.internal.reflect.JavaMethod</span></td><td><code>5541c31d24227b86</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.KryoBackedMessageSerializer</span></td><td><code>0028157720ec1f27</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.ConnectionSet</span></td><td><code>323708d9214e34e4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.ConnectionState</span></td><td><code>250fb1b274991d9a</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.DefaultMethodArgsSerializer</span></td><td><code>b5f4b38125033ffd</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.DefaultMethodArgsSerializer.ArraySerializer</span></td><td><code>16505d5ccbb1b78b</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.DefaultMethodArgsSerializer.EmptyArraySerializer</span></td><td><code>cdc53c79a631aa33</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.IncomingQueue</span></td><td><code>0e8ecdb8f31efe51</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.InterHubMessageSerializer</span></td><td><code>7d84d4aa85858c73</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.InterHubMessageSerializer.MessageReader</span></td><td><code>ab1cd6753eb75a29</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.InterHubMessageSerializer.MessageWriter</span></td><td><code>3e4611f758508afb</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.JavaSerializationBackedMethodArgsSerializer</span></td><td><code>4c7a738ee4525ff6</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHub</span></td><td><code>1326887a1f1da0ac</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHub.ChannelDispatch</span></td><td><code>8a9dfd1b6306d8e6</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHub.ConnectionDispatch</span></td><td><code>df1d0a86180d66e4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHub.ConnectionReceive</span></td><td><code>e1dc78071e8e957d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHub.Discard</span></td><td><code>63a8d677cc1f9101</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHub.Handler</span></td><td><code>3d232f51f2c02828</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHub.State</span></td><td><code>1b76747d7bce6b89</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHubBackedClient</span></td><td><code>77c2124c3c43d832</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection</span></td><td><code>c23964928f1aff22</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection.1</span></td><td><code>ac806a6bc6b1b21d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection.2</span></td><td><code>8ac38215966e3a20</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection.DispatchWrapper</span></td><td><code>9aa5d8679dbc6601</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MethodInvocationSerializer</span></td><td><code>47063ab293644e83</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MethodInvocationSerializer.MethodDetails</span></td><td><code>b6b7fb55e88cc4b9</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MethodInvocationSerializer.MethodInvocationReader</span></td><td><code>e6b939136f207ff5</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.MethodInvocationSerializer.MethodInvocationWriter</span></td><td><code>c3b77db1b2556afe</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.OutgoingQueue</span></td><td><code>fbcc05506ad40c68</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.protocol.ChannelIdentifier</span></td><td><code>7697ff6a7c712869</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.protocol.ChannelMessage</span></td><td><code>9bff479666e58802</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.protocol.EndOfStream</span></td><td><code>f29ffed85365f7db</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.protocol.InterHubMessage</span></td><td><code>0c6e49b6ec077e16</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.protocol.InterHubMessage.Delivery</span></td><td><code>0652d09c2a7fd1ac</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.queue.EndPointQueue</span></td><td><code>8038a5636529123d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.queue.MultiChannelQueue</span></td><td><code>bcaac9c224068764</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.queue.MultiEndPointQueue</span></td><td><code>27222a892157733f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.queue.MultiEndPointQueue.1</span></td><td><code>44049b3edc682954</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.hub.queue.QueueInitializer</span></td><td><code>ad18361c23e679b1</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.MultiChoiceAddress</span></td><td><code>91381aa03cdd48e7</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.MultiChoiceAddressSerializer</span></td><td><code>7ffc395650705aaa</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.SocketBlockingUtil</span></td><td><code>c4937b9849ff6540</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.SocketConnectCompletion</span></td><td><code>0da46ac4ccd1c9ce</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.SocketConnection</span></td><td><code>4a6255ae3fb5a6e9</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.SocketConnection.1</span></td><td><code>6bd72dd3a4f955f4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.SocketConnection.SocketInputStream</span></td><td><code>41ab28127ec07333</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.SocketConnection.SocketOutputStream</span></td><td><code>56a19b1a3dd17be0</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.SocketInetAddress</span></td><td><code>20cc3fd7992230e8</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.SocketInetAddress.Serializer</span></td><td><code>d42dd7f644e6367c</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.internal.inet.TcpOutgoingConnector</span></td><td><code>da9b573729b690fd</code></td></tr><tr><td><span class="el_class">org.gradle.internal.remote.services.MessagingServices</span></td><td><code>371e97275eb35dc5</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.AbstractCollectionSerializer</span></td><td><code>7897b7a9a0c39b1b</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.AbstractDecoder</span></td><td><code>6f331f65d3691839</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.AbstractEncoder</span></td><td><code>44ea8279ea7b3a07</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.AbstractSerializer</span></td><td><code>d5cd8744f99ef12d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory</span></td><td><code>83d030ef5f6c0526</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.BigDecimalSerializer</span></td><td><code>4dcd516a5e9202d8</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.BigIntegerSerializer</span></td><td><code>50f6ab963a855e2a</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.BooleanSerializer</span></td><td><code>9a343eeb20f2b7f4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.ByteArraySerializer</span></td><td><code>9566e41ef84566ae</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.ByteSerializer</span></td><td><code>85286889be7534cf</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.CharSerializer</span></td><td><code>6fba21fa805857ff</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.DoubleSerializer</span></td><td><code>085b687ded9be124</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.EnumSerializer</span></td><td><code>a314f7118f1d0412</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.FileSerializer</span></td><td><code>c43bc85ad47073ee</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.FloatSerializer</span></td><td><code>dad7a35798e49d9d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.IntegerSerializer</span></td><td><code>e8a55740afa66954</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.LongSerializer</span></td><td><code>3a08a2ef15abca0d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.PathSerializer</span></td><td><code>22894f3c1859ada1</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.ShortSerializer</span></td><td><code>5791e5a0a4ad1ece</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.StringMapSerializer</span></td><td><code>eb348217154fd0ed</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.StringSerializer</span></td><td><code>01576ecfb2720760</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.BaseSerializerFactory.ThrowableSerializer</span></td><td><code>ae463fe767977ceb</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.ClassLoaderObjectInputStream</span></td><td><code>81d9f3a2338180d3</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializer</span></td><td><code>9b7593104d5f803c</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializerRegistry</span></td><td><code>84449bcf590c1af7</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializerRegistry.1</span></td><td><code>aeba2bb0cd2eab52</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializerRegistry.HierarchySerializerMatcher</span></td><td><code>c4fa93579434fd2b</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializerRegistry.InstanceBasedSerializerFactory</span></td><td><code>4d56c9c7fbddbcc0</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializerRegistry.SerializerClassMatcherStrategy</span></td><td><code>ea36ea8beff22743</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializerRegistry.StrictSerializerMatcher</span></td><td><code>6df6080c06573b93</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializerRegistry.TaggedTypeSerializer</span></td><td><code>264fbb605d976b35</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.DefaultSerializerRegistry.TypeInfo</span></td><td><code>bd6904d4ac5974ce</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.HashCodeSerializer</span></td><td><code>874be2a480b96af8</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.InputStreamBackedDecoder</span></td><td><code>1a43def6f05c6405</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.ListSerializer</span></td><td><code>bad970c0192233e9</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.kryo.KryoBackedDecoder</span></td><td><code>f9287092db21f40c</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.kryo.KryoBackedEncoder</span></td><td><code>e6a2be1dd138a272</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.kryo.TypeSafeSerializer</span></td><td><code>1dbc9e4c69fd1973</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.kryo.TypeSafeSerializer.1</span></td><td><code>bb88df969641a032</code></td></tr><tr><td><span class="el_class">org.gradle.internal.serialize.kryo.TypeSafeSerializer.2</span></td><td><code>599bac595545b9c0</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.AbstractServiceMethod</span></td><td><code>d8f9bf72435aa0d5</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceAccessToken</span></td><td><code>552643647739457e</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceMethodFactory</span></td><td><code>7cd5dc9e6187cc39</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry</span></td><td><code>5b91c2e6d1541db9</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.1</span></td><td><code>a5957bd6a7e35017</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.ClassInspector</span></td><td><code>a67f4de9fecf4e9f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.ClassInspector.ClassDetails</span></td><td><code>e14a78fdabafa6a6</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.CompositeServiceProvider</span></td><td><code>ad51f0e7303e801d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.ConstructorService</span></td><td><code>c53583a3bbee2329</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.FactoryMethodService</span></td><td><code>c94f704959a77ddb</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.FactoryService</span></td><td><code>5efa06cdad2305e5</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.FixedInstanceService</span></td><td><code>adf12b7e82dfb3fd</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.ManagedObjectServiceProvider</span></td><td><code>00ae876b89fa7714</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.OwnServices</span></td><td><code>d7eed1a352f0d8e6</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.ParentServices</span></td><td><code>8369f7999f6e2ac7</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.SingletonService</span></td><td><code>d319469c908bc1cf</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.SingletonService.1</span></td><td><code>cdfec069e7d8ab42</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.SingletonService.BindState</span></td><td><code>b653da4aa2ccd9df</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.State</span></td><td><code>76b519b0b74b53cb</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.DefaultServiceRegistry.ThisAsService</span></td><td><code>2eef9987bf419fb4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.InjectUtil</span></td><td><code>4e32c5f95305147b</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.MethodHandleBasedServiceMethod</span></td><td><code>674037aa99129b52</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.MethodHandleBasedServiceMethodFactory</span></td><td><code>47e87df4713e4ce5</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.ReflectionBasedServiceMethod</span></td><td><code>56fdba7d8393253f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.RelevantMethods</span></td><td><code>ebb3efee7040ae62</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.RelevantMethods.RelevantMethodsBuilder</span></td><td><code>653e2ed3e283822f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.ServiceAccess</span></td><td><code>eae993853cb06bb6</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.ServiceAccess.1</span></td><td><code>c3b7931689739967</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.ServiceRegistryBuilder</span></td><td><code>73d1a49b155b6056</code></td></tr><tr><td><span class="el_class">org.gradle.internal.service.TypeStringFormatter</span></td><td><code>8ef195776e72517f</code></td></tr><tr><td><span class="el_class">org.gradle.internal.time.MonotonicClock</span></td><td><code>723fd7c85fffe54b</code></td></tr><tr><td><span class="el_class">org.gradle.internal.time.Time</span></td><td><code>118854647ab7eed4</code></td></tr><tr><td><span class="el_class">org.gradle.internal.time.TimeSource</span></td><td><code>79d456cc39bbde3d</code></td></tr><tr><td><span class="el_class">org.gradle.internal.time.TimeSource.1</span></td><td><code>4be788ff9e4278cd</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.WorkerLoggingSerializer</span></td><td><code>adae78bad8b0e727</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.ActionExecutionWorker</span></td><td><code>a7d30aba9c762788</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.ActionExecutionWorker.1</span></td><td><code>d0eba6bfe3f78d57</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.DefaultWorkerDirectoryProvider</span></td><td><code>10469cccf2e081cb</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker</span></td><td><code>d068f629bd86a048</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.2</span></td><td><code>6c72a17e07c7b1b9</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.ContextImpl</span></td><td><code>ceb41a6c2d98061a</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.PrintUnrecoverableErrorToFileHandler</span></td><td><code>e4f71133f397bb64</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.WorkerServices</span></td><td><code>186bde299ffe3d06</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.WorkerServices.1</span></td><td><code>69593c5a4d4ba8bb</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.child.WorkerLogEventListener</span></td><td><code>4a0b5fb708591833</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.messaging.WorkerConfig</span></td><td><code>8633f06980fb19fd</code></td></tr><tr><td><span class="el_class">org.gradle.process.internal.worker.messaging.WorkerConfigSerializer</span></td><td><code>b913cc847f396960</code></td></tr><tr><td><span class="el_class">org.gradle.util.internal.ArrayUtils</span></td><td><code>05a97ebb2e812055</code></td></tr><tr><td><span class="el_class">org.gradle.util.internal.CollectionUtils</span></td><td><code>9f70df10e2ea4351</code></td></tr><tr><td><span class="el_class">org.jsoup.Jsoup</span></td><td><code>16106553a5fa1d52</code></td></tr><tr><td><span class="el_class">org.jsoup.helper.ChangeNotifyingArrayList</span></td><td><code>99c90fe7dbe5aa10</code></td></tr><tr><td><span class="el_class">org.jsoup.helper.DataUtil</span></td><td><code>ef4d3b5196f9f537</code></td></tr><tr><td><span class="el_class">org.jsoup.helper.Validate</span></td><td><code>7d50cf0a16a948b4</code></td></tr><tr><td><span class="el_class">org.jsoup.internal.Normalizer</span></td><td><code>cb4cabbf39d8e337</code></td></tr><tr><td><span class="el_class">org.jsoup.internal.StringUtil</span></td><td><code>1f6e32f871513624</code></td></tr><tr><td><span class="el_class">org.jsoup.internal.StringUtil.1</span></td><td><code>1fc3c94585438854</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Attributes</span></td><td><code>0a37b7b339a9304e</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Comment</span></td><td><code>7aa4040200b65b29</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.DataNode</span></td><td><code>f8b60ed0702972a8</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Document</span></td><td><code>9758114c02b7c6da</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Document.OutputSettings</span></td><td><code>56dac21df5938c39</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Document.OutputSettings.Syntax</span></td><td><code>7a6d9f8061708e20</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Document.QuirksMode</span></td><td><code>caecfcb5b4c982a8</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.DocumentType</span></td><td><code>5c0d8864fc35761c</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Element</span></td><td><code>d12acb5a7900008a</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Element.NodeList</span></td><td><code>bc6693e20b8f6ec4</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Entities</span></td><td><code>548b452a5ec82036</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Entities.CoreCharset</span></td><td><code>e67ea4e819218f39</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Entities.EscapeMode</span></td><td><code>cc41bdc76d369551</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.EntitiesData</span></td><td><code>e3d9f9c53a895a0d</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.LeafNode</span></td><td><code>2ab135d8862e83d1</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Node</span></td><td><code>897e6484ee3f857f</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.Node.OuterHtmlVisitor</span></td><td><code>79c4733794b204ec</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.NodeUtils</span></td><td><code>4dcc491d4e982d40</code></td></tr><tr><td><span class="el_class">org.jsoup.nodes.TextNode</span></td><td><code>b5d6fe3431acaa35</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.CharacterReader</span></td><td><code>f3867948f93248e3</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilder</span></td><td><code>cef9d7cf78fcf7af</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState</span></td><td><code>69a7dc316eecb8c6</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.1</span></td><td><code>97f135c7cdf6f5b3</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.10</span></td><td><code>76e0e9e326f6d60a</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.11</span></td><td><code>af778cabb965eb54</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.12</span></td><td><code>6240bb4f2c499d7b</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.13</span></td><td><code>f67be9ca75303615</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.14</span></td><td><code>bd3b63377a016cd9</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.15</span></td><td><code>6466f8062a93e500</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.16</span></td><td><code>d1b0a4030e96bde7</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.17</span></td><td><code>605e05fb13b7e235</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.18</span></td><td><code>b5c291dc8af83162</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.19</span></td><td><code>4cf401e745b3b3ca</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.2</span></td><td><code>1f974ea504ca9586</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.20</span></td><td><code>6a365731bc700086</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.21</span></td><td><code>60065db31775458e</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.22</span></td><td><code>29b359d7cd483aaa</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.23</span></td><td><code>c4f191be4439fac6</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.24</span></td><td><code>51c0c2bfc6578aef</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.25</span></td><td><code>a96ae9c3ab13062c</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.3</span></td><td><code>553de00f5fe05208</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.4</span></td><td><code>8f092ef376359fda</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.5</span></td><td><code>2d99d1bd1faf7873</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.6</span></td><td><code>ada33039520058a7</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.7</span></td><td><code>6d58ca28ca876527</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.8</span></td><td><code>20c218c4773a228e</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.9</span></td><td><code>abefc9503e0e791f</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.HtmlTreeBuilderState.Constants</span></td><td><code>be84fe63177e5c68</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.ParseErrorList</span></td><td><code>96baf0fa54f30006</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.ParseSettings</span></td><td><code>2904fd34dc4af311</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Parser</span></td><td><code>63fc22f110880d89</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Tag</span></td><td><code>ae89d4394c423bf8</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token</span></td><td><code>97d3d91bd51206e4</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token.Character</span></td><td><code>3d98231a1bf660e2</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token.Comment</span></td><td><code>335d0c16d2253ac9</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token.Doctype</span></td><td><code>8f15c37e7212a835</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token.EOF</span></td><td><code>d92032ccef4338a0</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token.EndTag</span></td><td><code>0e37a248b7957ab0</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token.StartTag</span></td><td><code>4d2cd0c96a31ca6e</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token.Tag</span></td><td><code>87164321845893e3</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Token.TokenType</span></td><td><code>6f4f8143b566db2e</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Tokeniser</span></td><td><code>dc4b18e2dbb6a0e2</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.Tokeniser.1</span></td><td><code>85d14ff02cf580ff</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState</span></td><td><code>f31750b7891ee29d</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.1</span></td><td><code>248930fb66fa5cf3</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.10</span></td><td><code>695b68d6ee2b9499</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.11</span></td><td><code>2d66cd7f0bd3fee8</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.12</span></td><td><code>e9a4330ea58ce853</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.13</span></td><td><code>0ea465cde1868012</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.14</span></td><td><code>381e6ae36e68a728</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.15</span></td><td><code>b605227f0a3ccb62</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.16</span></td><td><code>da94151326962cb1</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.17</span></td><td><code>e7203926c941c722</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.18</span></td><td><code>87f3c05a0ca2b80a</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.19</span></td><td><code>167eb92acfa6d7af</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.2</span></td><td><code>db9e08e2dfa35c4f</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.20</span></td><td><code>2abc96239fcd626a</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.21</span></td><td><code>e081b26c60aa44db</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.22</span></td><td><code>255f0986033deacf</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.23</span></td><td><code>6aa23209274b0b8c</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.24</span></td><td><code>915da7119ebf725e</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.25</span></td><td><code>9d504ee744865da2</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.26</span></td><td><code>763910a0fa36b05f</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.27</span></td><td><code>95923293055a3966</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.28</span></td><td><code>d7146711c9c9d9cb</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.29</span></td><td><code>719827be47e631f9</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.3</span></td><td><code>b861ebf3e813e9f9</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.30</span></td><td><code>87300c584cdd6cda</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.31</span></td><td><code>a0fa16ffdfa18261</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.32</span></td><td><code>97698def83fee8b2</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.33</span></td><td><code>21589bd6b9088f45</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.34</span></td><td><code>121f4e6d2202173f</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.35</span></td><td><code>78f27921be022028</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.36</span></td><td><code>af38d6f2774d85f8</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.37</span></td><td><code>1635e676afd98a46</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.38</span></td><td><code>0389834afdfb1b79</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.39</span></td><td><code>07cf883719b49377</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.4</span></td><td><code>132b2d8d2e107563</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.40</span></td><td><code>9d92b3d466e2f31a</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.41</span></td><td><code>e6ebd00ab6eb71f4</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.42</span></td><td><code>95d3e8df65a9b6e7</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.43</span></td><td><code>e912e1671696aae8</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.44</span></td><td><code>77f5b55abe167543</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.45</span></td><td><code>08a29b98b40bac8d</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.46</span></td><td><code>a78ad1cb280e8b6a</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.47</span></td><td><code>d40c859e16713b64</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.48</span></td><td><code>eb817650ca798de2</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.49</span></td><td><code>5b5e60cb1921012a</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.5</span></td><td><code>efb4955fde46fa2d</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.50</span></td><td><code>cbd7f066e369bd48</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.51</span></td><td><code>25e39f8f6c2da4f2</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.52</span></td><td><code>82eca545d178a683</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.53</span></td><td><code>47bf769f058b85c9</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.54</span></td><td><code>26a11f829bd56ce2</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.55</span></td><td><code>5bc8f64a218252e8</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.56</span></td><td><code>ed0621d45eaf990d</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.57</span></td><td><code>39ee5e5734fa7e97</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.58</span></td><td><code>b0403a2fba98b99e</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.59</span></td><td><code>daf013874314eeef</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.6</span></td><td><code>e37e1c626c55aa0e</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.60</span></td><td><code>e43b2b1350b53ec7</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.61</span></td><td><code>26acde065574812f</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.62</span></td><td><code>22d755afb1a508b9</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.63</span></td><td><code>b3aba6cca4b82ad1</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.64</span></td><td><code>a82f88c5a083435a</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.65</span></td><td><code>13f1e6be18dc42c8</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.66</span></td><td><code>3d641c1dfcd1dc05</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.67</span></td><td><code>954f3a9f147fe7db</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.7</span></td><td><code>f1ee2c1ea71747fc</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.8</span></td><td><code>84f73f2506ae1804</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TokeniserState.9</span></td><td><code>4a775c711a47c679</code></td></tr><tr><td><span class="el_class">org.jsoup.parser.TreeBuilder</span></td><td><code>e608e57323d21659</code></td></tr><tr><td><span class="el_class">org.jsoup.select.Elements</span></td><td><code>4c47ab9a84dd5bcd</code></td></tr><tr><td><span class="el_class">org.jsoup.select.Evaluator</span></td><td><code>61800e7350df5fe8</code></td></tr><tr><td><span class="el_class">org.jsoup.select.Evaluator.Tag</span></td><td><code>c180e9a530a6085d</code></td></tr><tr><td><span class="el_class">org.jsoup.select.NodeTraversor</span></td><td><code>cf51ab61dd585047</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.AssertEquals</span></td><td><code>aea44e892a85baf5</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.AssertNotNull</span></td><td><code>e2884cd35b13d591</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.AssertThrows</span></td><td><code>b610f9f2b407653a</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.AssertTrue</span></td><td><code>f0647c0dd50bd0ca</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.AssertionUtils</span></td><td><code>6c2dda6a293d5c0e</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.Assertions</span></td><td><code>6fe7bd6cc744a068</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.DisplayNameGenerator</span></td><td><code>43ef10ce77605f90</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.DisplayNameGenerator.IndicativeSentences</span></td><td><code>9ab9100f1735780b</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores</span></td><td><code>f15d5590363b49dd</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.DisplayNameGenerator.Simple</span></td><td><code>e125babed463cf50</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.DisplayNameGenerator.Standard</span></td><td><code>1342164bd94b6208</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.TestInstance.Lifecycle</span></td><td><code>824d5aabc76a4e92</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.extension.ConditionEvaluationResult</span></td><td><code>35e14124a607c6e0</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.extension.ExtensionContext</span></td><td><code>7c86fcf714cebd4b</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.extension.ExtensionContext.Namespace</span></td><td><code>f54a3d73ed88c765</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.extension.ExtensionContext.Store</span></td><td><code>e944780f81f4a619</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.extension.InvocationInterceptor</span></td><td><code>4b39cae55e027ad8</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.api.extension.TestInstancePreDestroyCallback</span></td><td><code>3ae4d9d89c44523e</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.JupiterTestEngine</span></td><td><code>e2ae6d11698760d5</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.config.CachingJupiterConfiguration</span></td><td><code>7e7fdcd1b43926e2</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.config.DefaultJupiterConfiguration</span></td><td><code>3264494f5452162d</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.config.EnumConfigurationParameterConverter</span></td><td><code>bab380425dca8d4f</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.config.InstantiatingConfigurationParameterConverter</span></td><td><code>518e1c643c30f164</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.AbstractExtensionContext</span></td><td><code>92d1cbb782871d54</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor</span></td><td><code>6bd43b670d3d109a</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.ClassExtensionContext</span></td><td><code>e06c010a57c8fe35</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.ClassTestDescriptor</span></td><td><code>cbcf684282c128ae</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.DefaultTestInstanceFactoryContext</span></td><td><code>92e45b48982d1267</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.DisplayNameUtils</span></td><td><code>9775811d058104a0</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.ExtensionUtils</span></td><td><code>99569ba69e99ecc5</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.JupiterEngineDescriptor</span></td><td><code>4b5e361d03d8e2d8</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext</span></td><td><code>95abf612d36312bc</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.JupiterTestDescriptor</span></td><td><code>2edd5e083fbd4650</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.LifecycleMethodUtils</span></td><td><code>1338a13e54c3417d</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.MethodBasedTestDescriptor</span></td><td><code>95f79302a3c3bfc9</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.MethodExtensionContext</span></td><td><code>98cadb8bc0242165</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.TestInstanceLifecycleUtils</span></td><td><code>6fdb52a1560f3f07</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor</span></td><td><code>4a4b21b6873750d1</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.AbstractAnnotatedDescriptorWrapper</span></td><td><code>4c3051cd66ae8274</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.AbstractOrderingVisitor</span></td><td><code>2747cc3e148c57d9</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.AbstractOrderingVisitor.DescriptorWrapperOrderer</span></td><td><code>01a66e5fdf0989e5</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.ClassOrderingVisitor</span></td><td><code>dbd6c4e35e0c9807</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.ClassSelectorResolver</span></td><td><code>8192a76b48a09183</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.DefaultClassDescriptor</span></td><td><code>f1ea1aa229787830</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.DiscoverySelectorResolver</span></td><td><code>0493a46e2481b698</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.MethodFinder</span></td><td><code>943e016148cf5f16</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.MethodOrderingVisitor</span></td><td><code>1c057f4243f10c0e</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.MethodSelectorResolver</span></td><td><code>340dbe384622c6a6</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.MethodSelectorResolver.MethodType</span></td><td><code>c7fa66a429b1a169</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.MethodSelectorResolver.MethodType.1</span></td><td><code>d532d669ad917197</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.MethodSelectorResolver.MethodType.2</span></td><td><code>c4e9f930d2a0da9e</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.MethodSelectorResolver.MethodType.3</span></td><td><code>5582d95f7c8ba333</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.predicates.IsInnerClass</span></td><td><code>373ab803cd4c3f71</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.predicates.IsNestedTestClass</span></td><td><code>5b959cc55c1f4f91</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.predicates.IsPotentialTestContainer</span></td><td><code>4134835721917de5</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests</span></td><td><code>cb8d6d2ffc57432f</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.predicates.IsTestFactoryMethod</span></td><td><code>671448bb3682a8b7</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.predicates.IsTestMethod</span></td><td><code>53d31c5fa4778cc3</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.predicates.IsTestTemplateMethod</span></td><td><code>db07bccfb8a7df73</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.discovery.predicates.IsTestableMethod</span></td><td><code>8a0a6571eef022fb</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.ConditionEvaluator</span></td><td><code>3bb7965905521dbe</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.ConstructorInvocation</span></td><td><code>f53a3f70cf473b9d</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.DefaultExecutableInvoker</span></td><td><code>8974ffb77e1cf465</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.DefaultTestInstances</span></td><td><code>d098f31a49ce3334</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.InterceptingExecutableInvoker</span></td><td><code>ab30414a2f357de2</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.ReflectiveInterceptorCall</span></td><td><code>d077167b1b3ad9de</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.InvocationInterceptorChain</span></td><td><code>22131ce04612244a</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.InvocationInterceptorChain.InterceptedInvocation</span></td><td><code>b7b2338a38c29871</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.InvocationInterceptorChain.ValidatingInvocation</span></td><td><code>10e75cb36574e272</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.JupiterEngineExecutionContext</span></td><td><code>50163587b45842fe</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.JupiterEngineExecutionContext.Builder</span></td><td><code>b39a5e63227384a5</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.JupiterEngineExecutionContext.State</span></td><td><code>0766343b70481496</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.MethodInvocation</span></td><td><code>58ecfbfde557e87f</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.NamespaceAwareStore</span></td><td><code>bcf4aaea269ea610</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.ParameterResolutionUtils</span></td><td><code>cd7f5d13b9a64866</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.execution.TestInstancesProvider</span></td><td><code>2ebaf1272ef59bc5</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.AutoCloseExtension</span></td><td><code>b0c206c116575b65</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.DisabledCondition</span></td><td><code>ae3c30ff8f7ce050</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.ExtensionRegistry</span></td><td><code>9b67ba9d9cc3e810</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.MutableExtensionRegistry</span></td><td><code>2445286ad5728ba3</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.MutableExtensionRegistry.Entry</span></td><td><code>2c27256a5e9eeb22</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.RepeatedTestExtension</span></td><td><code>134b5213d4cb80ac</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.TempDirectory</span></td><td><code>797b378613d1c2c3</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.TempDirectory.Scope</span></td><td><code>0acad3073640ecc4</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.TestInfoParameterResolver</span></td><td><code>1977e7bd25ea66b8</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.TestReporterParameterResolver</span></td><td><code>24002b8886de1936</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.TimeoutConfiguration</span></td><td><code>1e8a45682947503e</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.TimeoutDurationParser</span></td><td><code>363c8531b5e8d2d4</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.extension.TimeoutExtension</span></td><td><code>62b73ea4d52632c9</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.support.JupiterThrowableCollectorFactory</span></td><td><code>46546a446de4c9c0</code></td></tr><tr><td><span class="el_class">org.junit.jupiter.engine.support.OpenTest4JAndJUnit4AwareThrowableCollector</span></td><td><code>de05466420699f92</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.function.Try</span></td><td><code>d1970dd64ce22fa4</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.function.Try.Failure</span></td><td><code>2135634bf7e7ad88</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.logging.LoggerFactory</span></td><td><code>7b57f78fc724ac54</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.logging.LoggerFactory.DelegatingLogger</span></td><td><code>835c9a026ac4df32</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.support.AnnotationSupport</span></td><td><code>3f97027aa7ba84bf</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.support.ReflectionSupport</span></td><td><code>a54544f01875c65f</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.AnnotationUtils</span></td><td><code>1da637c10cbda39c</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.ClassLoaderUtils</span></td><td><code>41a8b61339cf4862</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.ClassNamePatternFilterUtils</span></td><td><code>484cfeb1dc3b6e93</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.ClassUtils</span></td><td><code>76e509e75c23c314</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.ClasspathScanner</span></td><td><code>23726102aeb1c0ca</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.CollectionUtils</span></td><td><code>6cb29bed5c13bb1d</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.LruCache</span></td><td><code>fd8ff40dff05b112</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.Preconditions</span></td><td><code>99362b29a037afdc</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.ReflectionUtils</span></td><td><code>f1c34b50fa11302d</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode</span></td><td><code>ecf76570811abc50</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.StringUtils</span></td><td><code>372dfca6fa74c1ea</code></td></tr><tr><td><span class="el_class">org.junit.platform.commons.util.UnrecoverableExceptions</span></td><td><code>2ffa834710d16849</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.CompositeFilter</span></td><td><code>cecade1862d00032</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.CompositeFilter.1</span></td><td><code>ed62b01b8b763511</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.EngineDiscoveryListener</span></td><td><code>22e0c8566a0701f5</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.EngineDiscoveryListener.1</span></td><td><code>a4cdbe8dd38d8f57</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.EngineExecutionListener</span></td><td><code>73f93afa543ca74a</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.EngineExecutionListener.1</span></td><td><code>999902b68f81dd9a</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.ExecutionRequest</span></td><td><code>83d3937d6c2e4880</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.Filter</span></td><td><code>c8d2fba4bb555492</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.FilterResult</span></td><td><code>1b4753bc794e8388</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.SelectorResolutionResult</span></td><td><code>02b8934961bdea6f</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.SelectorResolutionResult.Status</span></td><td><code>22866b13273482bd</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.TestDescriptor</span></td><td><code>a5f736d88185f693</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.TestDescriptor.Type</span></td><td><code>8530bdf90ae8fa6b</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.TestExecutionResult</span></td><td><code>29479a0ae9db2840</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.TestExecutionResult.Status</span></td><td><code>9e54a6b249ea167a</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.UniqueId</span></td><td><code>a5ee26b408b26ea1</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.UniqueId.Segment</span></td><td><code>03b0ccd7b69c9fc2</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.UniqueIdFormat</span></td><td><code>455c5b5eb27d1f81</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.discovery.ClassSelector</span></td><td><code>a6b2f6753eef3ff0</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.discovery.DiscoverySelectors</span></td><td><code>8b2cda0d30b056d5</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.discovery.MethodSelector</span></td><td><code>6c04eeae9238dc1c</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.descriptor.AbstractTestDescriptor</span></td><td><code>17948dfe4c8d10c0</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.descriptor.ClassSource</span></td><td><code>02f1eaa5b7b685aa</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.descriptor.EngineDescriptor</span></td><td><code>4ba3237c7e6b18d6</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.descriptor.MethodSource</span></td><td><code>ace231244bac7856</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.ClassContainerSelectorResolver</span></td><td><code>fde8e703faa64574</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution</span></td><td><code>4937cdb1a041a120</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolution.DefaultContext</span></td><td><code>151f6fae99911199</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver</span></td><td><code>0ad8786b2f016b1a</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver.Builder</span></td><td><code>29666059b7fb2614</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver.DefaultInitializationContext</span></td><td><code>1b0484c56b030686</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.SelectorResolver</span></td><td><code>458b4a4e46b6b868</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.SelectorResolver.Match</span></td><td><code>3c16d4dff276f57a</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.SelectorResolver.Match.Type</span></td><td><code>bd8402e1232e1a40</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.discovery.SelectorResolver.Resolution</span></td><td><code>ba500d6ba9a79953</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.ExclusiveResource</span></td><td><code>fa0dc5b65de1b0a0</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.ExclusiveResource.LockMode</span></td><td><code>705f9e9f579aeab0</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine</span></td><td><code>599b10c51fe35ea3</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor</span></td><td><code>a78f13d5e60b7d08</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.LockManager</span></td><td><code>99cdeecddb4ca68b</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.Node</span></td><td><code>3f2ca9c1749a3d5a</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.Node.SkipResult</span></td><td><code>42796aad70055913</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.NodeExecutionAdvisor</span></td><td><code>c8bf7d7bb2e19471</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.NodeTestTask</span></td><td><code>c4c004e32fc81aac</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.NodeTestTask.DefaultDynamicTestExecutor</span></td><td><code>9048d6cd4a8e05d3</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.NodeTestTaskContext</span></td><td><code>f68790b28827581e</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.NodeTreeWalker</span></td><td><code>c9f34e2fe83d5caa</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.NodeUtils</span></td><td><code>732ad1771b71d292</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.NodeUtils.1</span></td><td><code>5a44a7e2cbf864b4</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.NopLock</span></td><td><code>2234b58e6ffa6ea1</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService</span></td><td><code>512f5438a4d56505</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.SingleLock</span></td><td><code>f0bfd18cc662d7fc</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.hierarchical.ThrowableCollector</span></td><td><code>a891c129fd2a01df</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.store.NamespacedHierarchicalStore</span></td><td><code>fb95e61bdaf5378c</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.store.NamespacedHierarchicalStore.CompositeKey</span></td><td><code>a34d604b86522ded</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.store.NamespacedHierarchicalStore.EvaluatedValue</span></td><td><code>158ca2dfb82179f2</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.store.NamespacedHierarchicalStore.MemoizingSupplier</span></td><td><code>cf599a824eea18cf</code></td></tr><tr><td><span class="el_class">org.junit.platform.engine.support.store.NamespacedHierarchicalStore.StoredValue</span></td><td><code>3db67e78dde11925</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.EngineDiscoveryResult</span></td><td><code>c9df2add13bcb88f</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.EngineDiscoveryResult.Status</span></td><td><code>f767a377012b98ff</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.LauncherDiscoveryListener</span></td><td><code>088911f06a0807a1</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.LauncherDiscoveryListener.1</span></td><td><code>d946f222ae757dc1</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.LauncherSessionListener</span></td><td><code>694596eba9b0c85e</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.LauncherSessionListener.1</span></td><td><code>44b3640faa83f474</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.TestExecutionListener</span></td><td><code>f2d1545415335fda</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.TestIdentifier</span></td><td><code>fef478667b042ad5</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.TestPlan</span></td><td><code>f4faaefa1c9341ff</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.CompositeEngineExecutionListener</span></td><td><code>8321b18dbabfecb3</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.CompositeTestExecutionListener</span></td><td><code>04623991ddcc58da</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.DefaultDiscoveryRequest</span></td><td><code>e6360c7333fc842d</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.DefaultLauncher</span></td><td><code>95dfc056bdb1d2d2</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.DefaultLauncherConfig</span></td><td><code>b4cd1fb6724efdef</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.DefaultLauncherSession</span></td><td><code>e040225d1f67e564</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.DefaultLauncherSession.1</span></td><td><code>bcc6e4bee671ccad</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.DefaultLauncherSession.ClosedLauncher</span></td><td><code>aa7b08954aea78f5</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.DelegatingEngineExecutionListener</span></td><td><code>61bd4de41739236f</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.DelegatingLauncher</span></td><td><code>cdc9b7e4e1a576e0</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.EngineDiscoveryOrchestrator</span></td><td><code>e4e806767310de9f</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.EngineDiscoveryOrchestrator.Phase</span></td><td><code>67a547a561eee0f4</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.EngineDiscoveryResultValidator</span></td><td><code>552f6c5833b8f8bd</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.EngineExecutionOrchestrator</span></td><td><code>6e65b7c784b87efc</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.EngineFilterer</span></td><td><code>53cf37d963f8b58c</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.EngineIdValidator</span></td><td><code>b0288378227ce052</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.ExecutionListenerAdapter</span></td><td><code>d91ab455a4c89f9b</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.InternalTestPlan</span></td><td><code>ce2dcedb783e6f56</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.IterationOrder</span></td><td><code>452edacea5001e83</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.IterationOrder.1</span></td><td><code>0736e6add61b3334</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.IterationOrder.2</span></td><td><code>0d0d72b6e503c372</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherConfig</span></td><td><code>58100dc14c875cb9</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherConfig.Builder</span></td><td><code>64729ce9bd729578</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherConfigurationParameters</span></td><td><code>4b25c1714dc335ec</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherConfigurationParameters.Builder</span></td><td><code>ba76828012d853d8</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherConfigurationParameters.ParameterProvider</span></td><td><code>52b5b9d7814ff3b1</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherConfigurationParameters.ParameterProvider.2</span></td><td><code>886f3c723ddb9556</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherConfigurationParameters.ParameterProvider.3</span></td><td><code>0d89b6f56eb4db06</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder</span></td><td><code>fb76ce235ae5247b</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherDiscoveryResult</span></td><td><code>9833a129382c2ccc</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherFactory</span></td><td><code>79c2095302f261bb</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.LauncherListenerRegistry</span></td><td><code>64d5f2a8ac991f94</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.ListenerRegistry</span></td><td><code>7fe9373f303770d1</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener</span></td><td><code>3840931f19c9d795</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.OutcomeDelayingEngineExecutionListener.Outcome</span></td><td><code>730ad6d2d5641536</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.ServiceLoaderRegistry</span></td><td><code>08e28076c727f8e6</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.ServiceLoaderTestEngineRegistry</span></td><td><code>771f386239bb3682</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.StackTracePruningEngineExecutionListener</span></td><td><code>f6f65efcfd071c8e</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.core.StreamInterceptingTestExecutionListener</span></td><td><code>0f855b867dc3eac4</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.listeners.UniqueIdTrackingListener</span></td><td><code>aaf56096b3079d53</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.listeners.discovery.AbortOnFailureLauncherDiscoveryListener</span></td><td><code>0a9a375bd99ca30a</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners</span></td><td><code>408046ed24478736</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.listeners.discovery.LauncherDiscoveryListeners.LauncherDiscoveryListenerType</span></td><td><code>bf2a372dcc5e43f7</code></td></tr><tr><td><span class="el_class">org.junit.platform.launcher.listeners.session.LauncherSessionListeners</span></td><td><code>46b8848a888d4d59</code></td></tr><tr><td><span class="el_class">org.mockito.Answers</span></td><td><code>afd86bd70185fc83</code></td></tr><tr><td><span class="el_class">org.mockito.ArgumentMatchers</span></td><td><code>30453f217beea892</code></td></tr><tr><td><span class="el_class">org.mockito.Mock.Strictness</span></td><td><code>009145878d43835a</code></td></tr><tr><td><span class="el_class">org.mockito.Mockito</span></td><td><code>d32ee9f8bbd0a12f</code></td></tr><tr><td><span class="el_class">org.mockito.MockitoAnnotations</span></td><td><code>74524dc0e5acf9f0</code></td></tr><tr><td><span class="el_class">org.mockito.configuration.DefaultMockitoConfiguration</span></td><td><code>b174879ae8ed115e</code></td></tr><tr><td><span class="el_class">org.mockito.exceptions.base.MockitoException</span></td><td><code>739f5a212493ecd4</code></td></tr><tr><td><span class="el_class">org.mockito.internal.MockedStaticImpl</span></td><td><code>0ddd8f20e599e4f9</code></td></tr><tr><td><span class="el_class">org.mockito.internal.MockitoCore</span></td><td><code>f49bfbc3fe5350e5</code></td></tr><tr><td><span class="el_class">org.mockito.internal.PremainAttach</span></td><td><code>2c55710b3d403cec</code></td></tr><tr><td><span class="el_class">org.mockito.internal.PremainAttachAccess</span></td><td><code>d2470bfde559a3f4</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.CaptorAnnotationProcessor</span></td><td><code>2f21a4570b50b64a</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.ClassPathLoader</span></td><td><code>173a7c62160e6dbf</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.DefaultDoNotMockEnforcer</span></td><td><code>6a7cb49285062e7d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.DefaultInjectionEngine</span></td><td><code>5622872b1b0aa27c</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.GlobalConfiguration</span></td><td><code>0df96c19dabdcfc0</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.IndependentAnnotationEngine</span></td><td><code>54aaab1155cc41fd</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.IndependentAnnotationEngine.1</span></td><td><code>288c2838a4732b5b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.InjectingAnnotationEngine</span></td><td><code>3402d3906098d7e2</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.MockAnnotationProcessor</span></td><td><code>f32d9954d5c65205</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.SpyAnnotationEngine</span></td><td><code>6b53375c8a8a5cc1</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.ConstructorInjection</span></td><td><code>19dcf00154169dc3</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.ConstructorInjection.SimpleArgumentResolver</span></td><td><code>80e845e136e4a645</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.MockInjection</span></td><td><code>ca48d2168eb0379e</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.MockInjection.OngoingMockInjection</span></td><td><code>7332cebf2a8af19f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.MockInjectionStrategy</span></td><td><code>c2617130af96367b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.MockInjectionStrategy.1</span></td><td><code>452f05e1d767d35b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.PropertyAndSetterInjection</span></td><td><code>47bbc3a0a738f758</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.SpyOnInjectedFieldsHandler</span></td><td><code>c215fb083e355a56</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.filter.NameBasedCandidateFilter</span></td><td><code>022739292a1ee0bb</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.filter.TerminalMockCandidateFilter</span></td><td><code>33c9b708ed3e7c78</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.filter.TypeBasedCandidateFilter</span></td><td><code>e42765a95b390b22</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.scanner.InjectMocksScanner</span></td><td><code>46136c11b8e2ed65</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.injection.scanner.MockScanner</span></td><td><code>9a1662242e3f2d24</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.plugins.DefaultMockitoPlugins</span></td><td><code>f2b7ceb1ff6789f3</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.plugins.DefaultPluginSwitch</span></td><td><code>bae35df711d1f747</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.plugins.PluginFileReader</span></td><td><code>f40c61def10749c5</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.plugins.PluginFinder</span></td><td><code>bd3cbb4ee283ccc1</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.plugins.PluginInitializer</span></td><td><code>7b55758cab21a0db</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.plugins.PluginLoader</span></td><td><code>1702b486e8f8c9ad</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.plugins.PluginRegistry</span></td><td><code>edba7ea1c6a85364</code></td></tr><tr><td><span class="el_class">org.mockito.internal.configuration.plugins.Plugins</span></td><td><code>b0a44acc68acdddb</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.DelegatingMethod</span></td><td><code>aa9a3605cadc5938</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.MockSettingsImpl</span></td><td><code>3040d7b3b87e5329</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.SuspendMethod</span></td><td><code>5807a496dfc9c4c6</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport</span></td><td><code>cc16221692a6ad40</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.BytecodeGenerator</span></td><td><code>b96181544d17b32a</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker</span></td><td><code>a1a0ac895421946d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator</span></td><td><code>4d280bd4890c7bfc</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.ParameterWritingVisitorWrapper</span></td><td><code>f687cffac707cab0</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.ParameterWritingVisitorWrapper.MethodParameterStrippingMethodVisitor</span></td><td><code>f73bf14929b93218</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.InlineBytecodeGenerator.ParameterWritingVisitorWrapper.ParameterAddingClassVisitor</span></td><td><code>d9e075cf41f65b6b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker</span></td><td><code>fd2a1bfc5bd84ad7</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.1</span></td><td><code>17cce78e4d94728c</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.InlineStaticMockControl</span></td><td><code>7e9032663f9b5f13</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockFeatures</span></td><td><code>3948e31575d7accd</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockMethodAdvice</span></td><td><code>f2359792dc3778e4</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockMethodAdvice.ConstructorShortcut</span></td><td><code>e37fbd5282bb870b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockMethodAdvice.ConstructorShortcut.1</span></td><td><code>d0e13ff90475a1a2</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockMethodAdvice.RealMethodCall</span></td><td><code>c2369bb294a6357d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockMethodAdvice.ReturnValueWrapper</span></td><td><code>00efacc0ab0c5045</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockMethodAdvice.SelfCallInfo</span></td><td><code>23361b97116d3bc6</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockMethodAdvice.StaticMethodCall</span></td><td><code>1fdc5ce187749738</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.MockMethodInterceptor</span></td><td><code>0cc689df0bc804c0</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.ModuleHandler</span></td><td><code>a9609957ab4bbbbb</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.ModuleHandler.ModuleSystemFound</span></td><td><code>8d938a4ac4779d75</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.StackWalkerChecker</span></td><td><code>68e569e3f7178506</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.SubclassBytecodeGenerator</span></td><td><code>4c19068b417bbacb</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.SubclassInjectionLoader</span></td><td><code>b44aeab62a314e0f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.SubclassInjectionLoader.WithReflection</span></td><td><code>4fa50c5021fa78c0</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator</span></td><td><code>9b77b7f9f15ce65b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.MockitoMockKey</span></td><td><code>6ab1c48e921f0e50</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.bytebuddy.TypeCachingBytecodeGenerator.TypeCachingLock</span></td><td><code>f3718822abb34b6b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.instance.DefaultInstantiatorProvider</span></td><td><code>844386c7887007f1</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.instance.ObjenesisInstantiator</span></td><td><code>7a7c1771759c8b2f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.creation.settings.CreationSettings</span></td><td><code>1d27e65e4db303b9</code></td></tr><tr><td><span class="el_class">org.mockito.internal.debugging.LocationFactory</span></td><td><code>20c7d5b8c58d83b6</code></td></tr><tr><td><span class="el_class">org.mockito.internal.debugging.LocationFactory.DefaultLocationFactory</span></td><td><code>f8e464fb84825981</code></td></tr><tr><td><span class="el_class">org.mockito.internal.debugging.LocationImpl</span></td><td><code>530a4709e6947f60</code></td></tr><tr><td><span class="el_class">org.mockito.internal.debugging.LocationImpl.MetadataShim</span></td><td><code>e0e02617575176a4</code></td></tr><tr><td><span class="el_class">org.mockito.internal.exceptions.stacktrace.ConditionalStackTraceFilter</span></td><td><code>3ae97774773f8cc1</code></td></tr><tr><td><span class="el_class">org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleaner</span></td><td><code>ccefdaf75b25508d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.exceptions.stacktrace.DefaultStackTraceCleanerProvider</span></td><td><code>b96ca03f68c6b0bc</code></td></tr><tr><td><span class="el_class">org.mockito.internal.exceptions.stacktrace.StackTraceFilter</span></td><td><code>f1e5dd123e555b09</code></td></tr><tr><td><span class="el_class">org.mockito.internal.framework.DefaultMockitoFramework</span></td><td><code>9ff7a406a63b11b2</code></td></tr><tr><td><span class="el_class">org.mockito.internal.framework.DefaultMockitoSession</span></td><td><code>b769df66d2932b43</code></td></tr><tr><td><span class="el_class">org.mockito.internal.framework.DefaultMockitoSession.1</span></td><td><code>6d40a180fd180112</code></td></tr><tr><td><span class="el_class">org.mockito.internal.handler.InvocationNotifierHandler</span></td><td><code>81a88d2a9823ca2e</code></td></tr><tr><td><span class="el_class">org.mockito.internal.handler.MockHandlerFactory</span></td><td><code>60aaf611c9f037ba</code></td></tr><tr><td><span class="el_class">org.mockito.internal.handler.MockHandlerImpl</span></td><td><code>40af730c41726d19</code></td></tr><tr><td><span class="el_class">org.mockito.internal.handler.NullResultGuardian</span></td><td><code>887855f598dc7f26</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.ArgumentsProcessor</span></td><td><code>48a63d334fbe1568</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.DefaultInvocationFactory</span></td><td><code>fd7a2f1ca0abf244</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.InterceptedInvocation</span></td><td><code>2bc1759562590122</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.InterceptedInvocation.1</span></td><td><code>a808ee7e12b0c370</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.InvocationComparator</span></td><td><code>961ab6368446cd9c</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.InvocationMatcher</span></td><td><code>a60a277cde788c00</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.InvocationMatcher.1</span></td><td><code>d5c4b96c4388ebd1</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.MatcherApplicationStrategy</span></td><td><code>c26110ae251954b2</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.MatchersBinder</span></td><td><code>7855054a8c7718ee</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.StubInfoImpl</span></td><td><code>9766984c92e9959b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.TypeSafeMatching</span></td><td><code>db8fac8befb40512</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.finder.AllInvocationsFinder</span></td><td><code>a80f90b774f88fc5</code></td></tr><tr><td><span class="el_class">org.mockito.internal.invocation.mockref.MockWeakReference</span></td><td><code>1fbf38ee01ef223b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.junit.DefaultStubbingLookupListener</span></td><td><code>13dcfa4167b96780</code></td></tr><tr><td><span class="el_class">org.mockito.internal.junit.UniversalTestListener</span></td><td><code>b0cc5166693438a8</code></td></tr><tr><td><span class="el_class">org.mockito.internal.junit.UniversalTestListener.1</span></td><td><code>c4d90e297ae82cc7</code></td></tr><tr><td><span class="el_class">org.mockito.internal.junit.UnusedStubbings</span></td><td><code>b53675bd52d6172d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.junit.UnusedStubbingsFinder</span></td><td><code>675da25e30c00788</code></td></tr><tr><td><span class="el_class">org.mockito.internal.listeners.StubbingLookupNotifier</span></td><td><code>6f87fdb14780b091</code></td></tr><tr><td><span class="el_class">org.mockito.internal.listeners.StubbingLookupNotifier.Event</span></td><td><code>f6cc7c2930ac8f03</code></td></tr><tr><td><span class="el_class">org.mockito.internal.matchers.Any</span></td><td><code>ddca80baaf883a0c</code></td></tr><tr><td><span class="el_class">org.mockito.internal.matchers.ArrayEquals</span></td><td><code>27688bbbffae1861</code></td></tr><tr><td><span class="el_class">org.mockito.internal.matchers.Equals</span></td><td><code>84c89c09d537ae1d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.matchers.InstanceOf</span></td><td><code>5c0eab070e987a7e</code></td></tr><tr><td><span class="el_class">org.mockito.internal.matchers.LocalizedMatcher</span></td><td><code>f3da081806496e9c</code></td></tr><tr><td><span class="el_class">org.mockito.internal.progress.ArgumentMatcherStorageImpl</span></td><td><code>d9b8becac423331b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.progress.MockingProgressImpl</span></td><td><code>b4b478523e99786f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.progress.MockingProgressImpl.1</span></td><td><code>9f7db825fdcdf194</code></td></tr><tr><td><span class="el_class">org.mockito.internal.progress.SequenceNumber</span></td><td><code>a68ee1dd45f51b97</code></td></tr><tr><td><span class="el_class">org.mockito.internal.progress.ThreadSafeMockingProgress</span></td><td><code>452aa6e38ddff43e</code></td></tr><tr><td><span class="el_class">org.mockito.internal.progress.ThreadSafeMockingProgress.1</span></td><td><code>79ae9726492f0c4f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.session.DefaultMockitoSessionBuilder</span></td><td><code>b96a72121a8d44b7</code></td></tr><tr><td><span class="el_class">org.mockito.internal.session.MockitoLoggerAdapter</span></td><td><code>493d0e9955f91a80</code></td></tr><tr><td><span class="el_class">org.mockito.internal.session.MockitoSessionLoggerAdapter</span></td><td><code>f55620e96a8de521</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.BaseStubbing</span></td><td><code>7fb9abb0c3eadb7f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.ConsecutiveStubbing</span></td><td><code>557234368bf5ca41</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.DoAnswerStyleStubbing</span></td><td><code>6e7ca0308caa0784</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.InvocationContainerImpl</span></td><td><code>67208a7429ee203f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.OngoingStubbingImpl</span></td><td><code>747b28f7f0499aba</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.StrictnessSelector</span></td><td><code>00cb65fb01fdc580</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.StubbedInvocationMatcher</span></td><td><code>73693c29dbd5f1d4</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.StubbingComparator</span></td><td><code>8d9934f6f6956410</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.UnusedStubbingReporting</span></td><td><code>076cd36a765c131f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.answers.CallsRealMethods</span></td><td><code>e57edbc68b0e39e6</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.answers.DefaultAnswerValidator</span></td><td><code>bc157688cbf26d9c</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.answers.InvocationInfo</span></td><td><code>35c4a7d4431e2604</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.answers.Returns</span></td><td><code>419d0de7c8cd9ec2</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer</span></td><td><code>b4af5d0cc4127c43</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs</span></td><td><code>9290a19f5dbdf1b2</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.defaultanswers.ReturnsEmptyValues</span></td><td><code>d6ed669583d1bf96</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.defaultanswers.ReturnsMocks</span></td><td><code>99d9220ab6ee9e86</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.defaultanswers.ReturnsMoreEmptyValues</span></td><td><code>708bd411a28382b5</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.defaultanswers.ReturnsSmartNulls</span></td><td><code>f434f2f732e6e80e</code></td></tr><tr><td><span class="el_class">org.mockito.internal.stubbing.defaultanswers.TriesToReturnSelf</span></td><td><code>13e6f22c3923267d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.Checks</span></td><td><code>693b7ec3dc9db88a</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.ConsoleMockitoLogger</span></td><td><code>f6ec54a756328702</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.DefaultMockingDetails</span></td><td><code>445b7a7104a677ce</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.KotlinInlineClassUtil</span></td><td><code>8f2e65801baf9ad5</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.MockCreationValidator</span></td><td><code>83a10f2760252cf3</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.MockNameImpl</span></td><td><code>009cd5fc276ed0dd</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.MockUtil</span></td><td><code>8cedd1d6aa623c6c</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.ObjectMethodsGuru</span></td><td><code>e958146f93547352</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.Primitives</span></td><td><code>6b6a08aaf147839f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.StringUtil</span></td><td><code>0a51b9987b23cb8a</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.collections.HashCodeAndEqualsMockWrapper</span></td><td><code>93f7437facb707c5</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.collections.HashCodeAndEqualsSafeSet</span></td><td><code>737466b57a8efbc9</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.collections.HashCodeAndEqualsSafeSet.1</span></td><td><code>42fcd15141d4c88d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.collections.Iterables</span></td><td><code>f7eb3a38de601237</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.collections.Sets</span></td><td><code>31c2cf4c7d79f16e</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.DetachedThreadLocal</span></td><td><code>24c845c0cee0c23b</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.DetachedThreadLocal.1</span></td><td><code>defaf890898faa64</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.DetachedThreadLocal.Cleaner</span></td><td><code>fe82f09eda153c82</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.WeakConcurrentMap</span></td><td><code>317df0cbe9bf65e4</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.WeakConcurrentMap.LatentKey</span></td><td><code>49d0008ff01c2270</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.WeakConcurrentMap.WeakKey</span></td><td><code>c9b8ab481aee9c32</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.WeakConcurrentMap.WithInlinedExpunction</span></td><td><code>2900bb8f66594337</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.WeakConcurrentSet</span></td><td><code>01665a2956990716</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.concurrent.WeakConcurrentSet.Cleaner</span></td><td><code>8e47207f365780a7</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.io.IOUtil</span></td><td><code>85aaa73b6a20c3ce</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.FieldInitializationReport</span></td><td><code>afb34a6be447d8bd</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.FieldInitializer</span></td><td><code>5664ed6e482410e9</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.FieldInitializer.NoArgConstructorInstantiator</span></td><td><code>d226288ba79bdd6f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.FieldInitializer.ParameterizedConstructorInstantiator</span></td><td><code>41b0a2436abbee69</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.FieldInitializer.ParameterizedConstructorInstantiator.1</span></td><td><code>4c9971e1a5fee49f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.FieldReader</span></td><td><code>2d76f64107151601</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.GenericMetadataSupport</span></td><td><code>fa7ecde5970c0e5d</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.GenericMetadataSupport.FromClassGenericMetadataSupport</span></td><td><code>99c88b2ac93b8f3f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.GenericMetadataSupport.NotGenericReturnTypeSupport</span></td><td><code>8c611ef213f94120</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.InstrumentationMemberAccessor</span></td><td><code>51638cf41240c7ec</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.ModuleMemberAccessor</span></td><td><code>df6459cdb157634f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.util.reflection.SuperTypesLastSorter</span></td><td><code>edf0f566dfbec16f</code></td></tr><tr><td><span class="el_class">org.mockito.internal.verification.DefaultRegisteredInvocations</span></td><td><code>48184ff2108397ec</code></td></tr><tr><td><span class="el_class">org.mockito.junit.jupiter.MockitoExtension</span></td><td><code>15127f132851319e</code></td></tr><tr><td><span class="el_class">org.mockito.junit.jupiter.resolver.CaptorParameterResolver</span></td><td><code>2e8866c6a5127d6f</code></td></tr><tr><td><span class="el_class">org.mockito.junit.jupiter.resolver.CompositeParameterResolver</span></td><td><code>c7a126226052e288</code></td></tr><tr><td><span class="el_class">org.mockito.junit.jupiter.resolver.MockParameterResolver</span></td><td><code>2a46311d1d569871</code></td></tr><tr><td><span class="el_class">org.mockito.listeners.MockCreationListener</span></td><td><code>01f88456e5828efd</code></td></tr><tr><td><span class="el_class">org.mockito.mock.MockType</span></td><td><code>0b5105452bbd0790</code></td></tr><tr><td><span class="el_class">org.mockito.mock.SerializableMode</span></td><td><code>d8db118920e53367</code></td></tr><tr><td><span class="el_class">org.mockito.plugins.AnnotationEngine.NoAction</span></td><td><code>cccb6d6b2cd49244</code></td></tr><tr><td><span class="el_class">org.mockito.plugins.DoNotMockEnforcer</span></td><td><code>d84a80636a3b2091</code></td></tr><tr><td><span class="el_class">org.mockito.plugins.DoNotMockEnforcer.Cache</span></td><td><code>7ad724250d9a8fd2</code></td></tr><tr><td><span class="el_class">org.mockito.quality.Strictness</span></td><td><code>fd006704ba980aef</code></td></tr><tr><td><span class="el_class">org.objenesis.ObjenesisBase</span></td><td><code>0c1d2fd83029257f</code></td></tr><tr><td><span class="el_class">org.objenesis.ObjenesisStd</span></td><td><code>f35c83a75caea811</code></td></tr><tr><td><span class="el_class">org.objenesis.strategy.BaseInstantiatorStrategy</span></td><td><code>b0aaa6460452f5ce</code></td></tr><tr><td><span class="el_class">org.objenesis.strategy.StdInstantiatorStrategy</span></td><td><code>abae05ba56ea35a6</code></td></tr><tr><td><span class="el_class">org.slf4j.LoggerFactory</span></td><td><code>a381b7ddf19bf47d</code></td></tr><tr><td><span class="el_class">org.slf4j.LoggerFactory</span></td><td><code>45630a2ef9211bd2</code></td></tr><tr><td><span class="el_class">org.slf4j.MDC</span></td><td><code>21c0421f945a1458</code></td></tr><tr><td><span class="el_class">org.slf4j.bridge.SLF4JBridgeHandler</span></td><td><code>a24ab9068b3f1049</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.BasicMDCAdapter</span></td><td><code>354fafb117483fdb</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.BasicMDCAdapter.1</span></td><td><code>8f0671fb507009fb</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.BasicMarkerFactory</span></td><td><code>d8e0b7e9d11b515c</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.NOPLoggerFactory</span></td><td><code>eaf704972ef7000c</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.NOPLoggerFactory</span></td><td><code>54f5632bfcb8d8d5</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.NOPMDCAdapter</span></td><td><code>d816a97d0b663014</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.NOP_FallbackServiceProvider</span></td><td><code>44c4aa253bad3620</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.Reporter</span></td><td><code>c9b912a7116daa87</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.Reporter.Level</span></td><td><code>07530b930aa1c996</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.Reporter.TargetChoice</span></td><td><code>0aa347cd82827a6b</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.SubstituteLogger</span></td><td><code>9fbc5376f716d7cb</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.SubstituteLoggerFactory</span></td><td><code>2c5fb1b0f92b644d</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.SubstituteLoggerFactory</span></td><td><code>dc7efc0107a4a62d</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.SubstituteServiceProvider</span></td><td><code>c99ff2d1c9be6145</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.ThreadLocalMapOfStacks</span></td><td><code>2b24a935616f8730</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.Util</span></td><td><code>859d67cf0632e467</code></td></tr><tr><td><span class="el_class">org.slf4j.helpers.Util</span></td><td><code>857ff3acc0576435</code></td></tr><tr><td><span class="el_class">org.slf4j.impl.StaticLoggerBinder</span></td><td><code>6822bf7129d487fa</code></td></tr><tr><td><span class="el_class">org.springframework.aop.support.AopUtils</span></td><td><code>cf0e0af326d6831f</code></td></tr><tr><td><span class="el_class">org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryEventPublisherBeansApplicationListener</span></td><td><code>e038b74293c709e4</code></td></tr><tr><td><span class="el_class">org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryEventPublisherBeansTestExecutionListener</span></td><td><code>89df4f04e39e1522</code></td></tr><tr><td><span class="el_class">org.springframework.boot.logging.logback.RootLogLevelConfigurator</span></td><td><code>f395258742c62ae3</code></td></tr><tr><td><span class="el_class">org.springframework.context.annotation.Bean.Bootstrap</span></td><td><code>c04e70548a9210ce</code></td></tr><tr><td><span class="el_class">org.springframework.http.ContentDisposition</span></td><td><code>5b9d707726bfe522</code></td></tr><tr><td><span class="el_class">org.springframework.http.ContentDisposition.BuilderImpl</span></td><td><code>5bb84d7934f6ca2c</code></td></tr><tr><td><span class="el_class">org.springframework.http.HttpEntity</span></td><td><code>9e768933cd7e053b</code></td></tr><tr><td><span class="el_class">org.springframework.http.HttpHeaders</span></td><td><code>235340fe217f3c93</code></td></tr><tr><td><span class="el_class">org.springframework.http.HttpStatus</span></td><td><code>12aa0e3ab2fabde8</code></td></tr><tr><td><span class="el_class">org.springframework.http.HttpStatus.Series</span></td><td><code>65e70f0d823c4208</code></td></tr><tr><td><span class="el_class">org.springframework.http.MediaType</span></td><td><code>fb19988e5311d0af</code></td></tr><tr><td><span class="el_class">org.springframework.http.MediaType.1</span></td><td><code>685a2d7fa916161c</code></td></tr><tr><td><span class="el_class">org.springframework.http.ReadOnlyHttpHeaders</span></td><td><code>fc3ea060c0ac74bf</code></td></tr><tr><td><span class="el_class">org.springframework.http.ResponseEntity</span></td><td><code>41c04d8a02a7e9f7</code></td></tr><tr><td><span class="el_class">org.springframework.mock.web.MockMultipartFile</span></td><td><code>80d549146ec43c9f</code></td></tr><tr><td><span class="el_class">org.springframework.test.context.bean.override.mockito.SpringMockResolver</span></td><td><code>dc74bdc03ebd8048</code></td></tr><tr><td><span class="el_class">org.springframework.util.Assert</span></td><td><code>2e1248d2d1526e84</code></td></tr><tr><td><span class="el_class">org.springframework.util.ClassUtils</span></td><td><code>ca4ab8b63048e08b</code></td></tr><tr><td><span class="el_class">org.springframework.util.CollectionUtils</span></td><td><code>702b1708f49908a6</code></td></tr><tr><td><span class="el_class">org.springframework.util.ConcurrentReferenceHashMap</span></td><td><code>722cd58749bce5da</code></td></tr><tr><td><span class="el_class">org.springframework.util.ConcurrentReferenceHashMap.ReferenceManager</span></td><td><code>35eb6b9c1f2eedb5</code></td></tr><tr><td><span class="el_class">org.springframework.util.ConcurrentReferenceHashMap.ReferenceType</span></td><td><code>5b823be241865c2f</code></td></tr><tr><td><span class="el_class">org.springframework.util.ConcurrentReferenceHashMap.Segment</span></td><td><code>5daee5d71f2a6fe2</code></td></tr><tr><td><span class="el_class">org.springframework.util.FileCopyUtils</span></td><td><code>0f3e1cb291fffdb1</code></td></tr><tr><td><span class="el_class">org.springframework.util.LinkedCaseInsensitiveMap</span></td><td><code>52504fea98decea2</code></td></tr><tr><td><span class="el_class">org.springframework.util.LinkedCaseInsensitiveMap.1</span></td><td><code>91801a6f81bb94f9</code></td></tr><tr><td><span class="el_class">org.springframework.util.LinkedMultiValueMap</span></td><td><code>7d417a227f0e3c42</code></td></tr><tr><td><span class="el_class">org.springframework.util.MimeType</span></td><td><code>e4d1ce0eae1d0476</code></td></tr><tr><td><span class="el_class">org.springframework.util.MimeType.SpecificityComparator</span></td><td><code>e99b29c1a1762df2</code></td></tr><tr><td><span class="el_class">org.springframework.util.MultiValueMapAdapter</span></td><td><code>7d7830d4d4d7a9dd</code></td></tr><tr><td><span class="el_class">org.springframework.util.StringUtils</span></td><td><code>942b8fade34702e5</code></td></tr><tr><td><span class="el_class">org.springframework.web.multipart.MultipartFile</span></td><td><code>7d0a393dab1e2ac4</code></td></tr><tr><td><a href="stirling.software.SPDF.config/InstallationPathConfig.html" class="el_class">stirling.software.SPDF.config.InstallationPathConfig</a></td><td><code>d829eb9b6c21aa0f</code></td></tr><tr><td><a href="stirling.software.SPDF.config/LogbackPropertyLoader.html" class="el_class">stirling.software.SPDF.config.LogbackPropertyLoader</a></td><td><code>4842ae34adb0d3ac</code></td></tr><tr><td><a href="stirling.software.SPDF.config/RuntimePathConfig.html" class="el_class">stirling.software.SPDF.config.RuntimePathConfig</a></td><td><code>1d3eb4e5f779e7b8</code></td></tr><tr><td><a href="stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.html" class="el_class">stirling.software.SPDF.controller.api.converters.ConvertMarkdownToPdf</a></td><td><code>ee7ba6121cb17a96</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.controller.api.converters.ConvertMarkdownToPdfTest</span></td><td><code>3b0bc5beded4cbaa</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.controller.api.converters.ConvertPDFToMarkdownTest</span></td><td><code>c78948b7769ca66d</code></td></tr><tr><td><a href="stirling.software.SPDF.controller.api.converters/TableAttributeProvider.html" class="el_class">stirling.software.SPDF.controller.api.converters.TableAttributeProvider</a></td><td><code>8ea0542591fc02c1</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties</a></td><td><code>e14af21146854e84</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.AutoPipeline</span></td><td><code>b37c06d10194993c</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.AutomaticallyGenerated</span></td><td><code>07581c5103bbc871</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.CustomPaths</span></td><td><code>3f995b544580ff58</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Operations</span></td><td><code>28b4f8a0af25fc7e</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Pipeline</span></td><td><code>136d35f491e8e893</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.Endpoints</span></td><td><code>c68768754140d77b</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.EnterpriseEdition</span></td><td><code>f80165fcd060f8a3</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$EnterpriseEdition$CustomMetadata.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.EnterpriseEdition.CustomMetadata</a></td><td><code>1b858820f7e06e27</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.Legal</span></td><td><code>4a6583a060d61716</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.Metrics</span></td><td><code>f439b8507a78843f</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.Premium</span></td><td><code>4530fd71b8f9a5b7</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.Premium.EnterpriseFeatures</span></td><td><code>0634d6e9062a98cd</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.Premium.EnterpriseFeatures.PersistentMetrics</span></td><td><code>7390e98d9f10e0fa</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures</span></td><td><code>056b686c5f5bca2f</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$CustomMetadata.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures.CustomMetadata</a></td><td><code>a56590bfc215427b</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$GoogleDrive.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures.GoogleDrive</a></td><td><code>b84d5c9a04c8aaa3</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.ProcessExecutor</span></td><td><code>503dfc507a54ea3e</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$SessionLimit.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.ProcessExecutor.SessionLimit</a></td><td><code>3d922a1131346441</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$TimeoutMinutes.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.ProcessExecutor.TimeoutMinutes</a></td><td><code>4037d9e48d7a1932</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$Security.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.Security</a></td><td><code>1a32644f541d33a8</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.ApplicationProperties.Security.InitialLogin</span></td><td><code>97a6540585d99b1a</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2</a></td><td><code>0aff21f08ef56bde</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2$Client.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client</a></td><td><code>470c7f41308f4cb4</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$Security$SAML2.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.Security.SAML2</a></td><td><code>7c4a409750d1d64f</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$System.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.System</a></td><td><code>046c56f28c3961b1</code></td></tr><tr><td><a href="stirling.software.SPDF.model/ApplicationProperties$Ui.html" class="el_class">stirling.software.SPDF.model.ApplicationProperties.Ui</a></td><td><code>a174bcc1f58202cb</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.api.GeneralFile</span></td><td><code>421af46d1a2378f7</code></td></tr><tr><td><span class="el_class">stirling.software.SPDF.model.api.PDFFile</span></td><td><code>76272cacd74bebc3</code></td></tr><tr><td><a href="stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.html" class="el_class">stirling.software.SPDF.model.api.converters.ConvertPDFToMarkdown</a></td><td><code>6d7986aa335640ae</code></td></tr><tr><td><a href="stirling.software.SPDF.model.provider/GitHubProvider.html" class="el_class">stirling.software.SPDF.model.provider.GitHubProvider</a></td><td><code>e44568f2e920afba</code></td></tr><tr><td><a href="stirling.software.SPDF.model.provider/GoogleProvider.html" class="el_class">stirling.software.SPDF.model.provider.GoogleProvider</a></td><td><code>578263518194e126</code></td></tr><tr><td><a href="stirling.software.SPDF.model.provider/KeycloakProvider.html" class="el_class">stirling.software.SPDF.model.provider.KeycloakProvider</a></td><td><code>ca65d20a626d7c89</code></td></tr><tr><td><a href="stirling.software.SPDF.model.provider/Provider.html" class="el_class">stirling.software.SPDF.model.provider.Provider</a></td><td><code>bd5529acc0d790f1</code></td></tr><tr><td><a href="stirling.software.SPDF.service/CustomPDFDocumentFactory.html" class="el_class">stirling.software.SPDF.service.CustomPDFDocumentFactory</a></td><td><code>3c793475e99e4cb6</code></td></tr><tr><td><a href="stirling.software.SPDF.utils/PDFToFile.html" class="el_class">stirling.software.SPDF.utils.PDFToFile</a></td><td><code>17a29eb473128e40</code></td></tr><tr><td><a href="stirling.software.SPDF.utils/ProcessExecutor.html" class="el_class">stirling.software.SPDF.utils.ProcessExecutor</a></td><td><code>9c2893180aa0a054</code></td></tr><tr><td><a href="stirling.software.SPDF.utils/ProcessExecutor$ProcessExecutorResult.html" class="el_class">stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult</a></td><td><code>60706bbf6d93f17f</code></td></tr><tr><td><a href="stirling.software.SPDF.utils/ProcessExecutor$Processes.html" class="el_class">stirling.software.SPDF.utils.ProcessExecutor.Processes</a></td><td><code>42c1279d49d44121</code></td></tr><tr><td><a href="stirling.software.SPDF.utils/WebResponseUtils.html" class="el_class">stirling.software.SPDF.utils.WebResponseUtils</a></td><td><code>ae7ae240ec473ca2</code></td></tr><tr><td><span class="el_class">sun.text.resources.cldr.ext.FormatData_zh</span></td><td><code>b4c03ae1617c274f</code></td></tr><tr><td><span class="el_class">sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo</span></td><td><code>c645efede4697974</code></td></tr><tr><td><span class="el_class">sun.util.resources.provider.LocaleDataProvider</span></td><td><code>dc2bfc1174cbb2fe</code></td></tr><tr><td><span class="el_class">sun.util.resources.provider.NonBaseLocaleDataMetaInfo</span></td><td><code>a328047bf3cdb7a5</code></td></tr><tr><td><span class="el_class">worker.org.gradle.api.JavaVersion</span></td><td><code>aaef7cd2313e04d9</code></td></tr><tr><td><span class="el_class">worker.org.gradle.api.internal.jvm.JavaVersionParser</span></td><td><code>1206b4dd1a2e9827</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.ClassLoaderSpec</span></td><td><code>cb374b01ccbebc0b</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.ClassLoaderUtils</span></td><td><code>8203100709821636</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.ClassLoaderUtils.AbstractClassLoaderLookuper</span></td><td><code>c285dc94ede87ba6</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.ClassLoaderUtils.Java9PackagesFetcher</span></td><td><code>66503273ab6df058</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.ClassLoaderUtils.LookupClassDefiner</span></td><td><code>101fed03f270a39f</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.FilteringClassLoader</span></td><td><code>685f3dec8c07e429</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.FilteringClassLoader.RetrieveSystemPackagesClassLoader</span></td><td><code>f37f538880fb8032</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.FilteringClassLoader.Spec</span></td><td><code>66254ecaab39094b</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.classloader.FilteringClassLoader.TrieSet</span></td><td><code>9ca6d89930a3c026</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.stream.EncodedStream.EncodedInput</span></td><td><code>9af7c11b2107c234</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.util.Trie</span></td><td><code>19fbee069a29feb3</code></td></tr><tr><td><span class="el_class">worker.org.gradle.internal.util.Trie.Builder</span></td><td><code>3ff89b3303eddda1</code></td></tr><tr><td><span class="el_class">worker.org.gradle.process.internal.worker.GradleWorkerMain</span></td><td><code>232767ef46e8d7ca</code></td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/CMSProcessableInputStream.html b/testResult/html/org.apache.pdfbox.examples.signature/CMSProcessableInputStream.html
    new file mode 100644
    index 000000000..9313b2f42
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/CMSProcessableInputStream.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CMSProcessableInputStream</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">org.apache.pdfbox.examples.signature</a> &gt; <span class="el_class">CMSProcessableInputStream</span></div><h1>CMSProcessableInputStream</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">33 of 33</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a1"><a href="CMSProcessableInputStream.java.html#L40" class="el_method">CMSProcessableInputStream(InputStream)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CMSProcessableInputStream.java.html#L43" class="el_method">CMSProcessableInputStream(ASN1ObjectIdentifier, InputStream)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="CMSProcessableInputStream.java.html#L56" class="el_method">write(OutputStream)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="CMSProcessableInputStream.java.html#L50" class="el_method">getContent()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="CMSProcessableInputStream.java.html#L62" class="el_method">getContentType()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/CMSProcessableInputStream.java.html b/testResult/html/org.apache.pdfbox.examples.signature/CMSProcessableInputStream.java.html
    new file mode 100644
    index 000000000..b506757b5
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/CMSProcessableInputStream.java.html
    @@ -0,0 +1,65 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CMSProcessableInputStream.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">org.apache.pdfbox.examples.signature</a> &gt; <span class="el_source">CMSProcessableInputStream.java</span></div><h1>CMSProcessableInputStream.java</h1><pre class="source lang-java linenums">/*
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    + * contributor license agreements.  See the NOTICE file distributed with
    + * this work for additional information regarding copyright ownership.
    + * The ASF licenses this file to You under the Apache License, Version 2.0
    + * (the &quot;License&quot;); you may not use this file except in compliance with
    + * the License.  You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package org.apache.pdfbox.examples.signature;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.OutputStream;
    +
    +import org.bouncycastle.asn1.ASN1ObjectIdentifier;
    +import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
    +import org.bouncycastle.cms.CMSException;
    +import org.bouncycastle.cms.CMSTypedData;
    +
    +/**
    + * Wraps a InputStream into a CMSProcessable object for bouncy castle. It's a memory saving
    + * alternative to the {@link org.bouncycastle.cms.CMSProcessableByteArray CMSProcessableByteArray}
    + * class.
    + *
    + * @author Thomas Chojecki
    + */
    +class CMSProcessableInputStream implements CMSTypedData {
    +    private final InputStream in;
    +    private final ASN1ObjectIdentifier contentType;
    +
    +    CMSProcessableInputStream(InputStream is) {
    +<span class="nc" id="L40">        this(new ASN1ObjectIdentifier(CMSObjectIdentifiers.data.getId()), is);</span>
    +<span class="nc" id="L41">    }</span>
    +
    +<span class="nc" id="L43">    CMSProcessableInputStream(ASN1ObjectIdentifier type, InputStream is) {</span>
    +<span class="nc" id="L44">        contentType = type;</span>
    +<span class="nc" id="L45">        in = is;</span>
    +<span class="nc" id="L46">    }</span>
    +
    +    @Override
    +    public Object getContent() {
    +<span class="nc" id="L50">        return in;</span>
    +    }
    +
    +    @Override
    +    public void write(OutputStream out) throws IOException, CMSException {
    +        // read the content only one time
    +<span class="nc" id="L56">        in.transferTo(out);</span>
    +<span class="nc" id="L57">        in.close();</span>
    +<span class="nc" id="L58">    }</span>
    +
    +    @Override
    +    public ASN1ObjectIdentifier getContentType() {
    +<span class="nc" id="L62">        return contentType;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/CreateSignatureBase.html b/testResult/html/org.apache.pdfbox.examples.signature/CreateSignatureBase.html
    new file mode 100644
    index 000000000..b7e9c0973
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/CreateSignatureBase.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CreateSignatureBase</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">org.apache.pdfbox.examples.signature</a> &gt; <span class="el_class">CreateSignatureBase</span></div><h1>CreateSignatureBase</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">147 of 147</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">42</td><td class="ctr2">42</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a7"><a href="CreateSignatureBase.java.html#L131" class="el_method">sign(InputStream)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="74" alt="74"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h0">16</td><td class="ctr2" id="i0">16</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CreateSignatureBase.java.html#L68" class="el_method">CreateSignatureBase(KeyStore, char[])</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="82" height="10" title="51" alt="51"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h1">16</td><td class="ctr2" id="i1">16</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="CreateSignatureBase.java.html#L97" class="el_method">setPrivateKey(PrivateKey)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="CreateSignatureBase.java.html#L105" class="el_method">setCertificateChain(Certificate[])</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a6"><a href="CreateSignatureBase.java.html#L109" class="el_method">setTsaUrl(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a4"><a href="CreateSignatureBase.java.html#L168" class="el_method">setExternalSigning(boolean)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">2</td><td class="ctr2" id="i5">2</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a1"><a href="CreateSignatureBase.java.html#L101" class="el_method">getCertificateChain()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="CreateSignatureBase.java.html#L156" class="el_method">isExternalSigning()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/CreateSignatureBase.java.html b/testResult/html/org.apache.pdfbox.examples.signature/CreateSignatureBase.java.html
    new file mode 100644
    index 000000000..a1e9aa0d2
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/CreateSignatureBase.java.html
    @@ -0,0 +1,171 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CreateSignatureBase.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">org.apache.pdfbox.examples.signature</a> &gt; <span class="el_source">CreateSignatureBase.java</span></div><h1>CreateSignatureBase.java</h1><pre class="source lang-java linenums">/*
    + * Copyright 2015 The Apache Software Foundation.
    + *
    + * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.apache.pdfbox.examples.signature;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.URISyntaxException;
    +import java.security.GeneralSecurityException;
    +import java.security.KeyStore;
    +import java.security.KeyStoreException;
    +import java.security.NoSuchAlgorithmException;
    +import java.security.PrivateKey;
    +import java.security.UnrecoverableKeyException;
    +import java.security.cert.Certificate;
    +import java.security.cert.CertificateException;
    +import java.security.cert.X509Certificate;
    +import java.util.Arrays;
    +import java.util.Enumeration;
    +
    +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
    +import org.bouncycastle.cert.jcajce.JcaCertStore;
    +import org.bouncycastle.cms.CMSException;
    +import org.bouncycastle.cms.CMSSignedData;
    +import org.bouncycastle.cms.CMSSignedDataGenerator;
    +import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
    +import org.bouncycastle.operator.ContentSigner;
    +import org.bouncycastle.operator.OperatorCreationException;
    +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
    +import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
    +
    +public abstract class CreateSignatureBase implements SignatureInterface {
    +    private PrivateKey privateKey;
    +    private Certificate[] certificateChain;
    +    private String tsaUrl;
    +    private boolean externalSigning;
    +
    +    /**
    +     * Initialize the signature creator with a keystore (pkcs12) and pin that should be used for the
    +     * signature.
    +     *
    +     * @param keystore is a pkcs12 keystore.
    +     * @param pin is the pin for the keystore / private key
    +     * @throws KeyStoreException if the keystore has not been initialized (loaded)
    +     * @throws NoSuchAlgorithmException if the algorithm for recovering the key cannot be found
    +     * @throws UnrecoverableKeyException if the given password is wrong
    +     * @throws CertificateException if the certificate is not valid as signing time
    +     * @throws IOException if no certificate could be found
    +     */
    +    public CreateSignatureBase(KeyStore keystore, char[] pin)
    +            throws KeyStoreException,
    +                    UnrecoverableKeyException,
    +                    NoSuchAlgorithmException,
    +                    IOException,
    +<span class="nc" id="L68">                    CertificateException {</span>
    +        // grabs the first alias from the keystore and get the private key. An
    +        // alternative method or constructor could be used for setting a specific
    +        // alias that should be used.
    +<span class="nc" id="L72">        Enumeration&lt;String&gt; aliases = keystore.aliases();</span>
    +        String alias;
    +<span class="nc" id="L74">        Certificate cert = null;</span>
    +<span class="nc bnc" id="L75" title="All 4 branches missed.">        while (cert == null &amp;&amp; aliases.hasMoreElements()) {</span>
    +<span class="nc" id="L76">            alias = aliases.nextElement();</span>
    +<span class="nc" id="L77">            setPrivateKey((PrivateKey) keystore.getKey(alias, pin));</span>
    +<span class="nc" id="L78">            Certificate[] certChain = keystore.getCertificateChain(alias);</span>
    +<span class="nc bnc" id="L79" title="All 2 branches missed.">            if (certChain != null) {</span>
    +<span class="nc" id="L80">                setCertificateChain(certChain);</span>
    +<span class="nc" id="L81">                cert = certChain[0];</span>
    +<span class="nc bnc" id="L82" title="All 2 branches missed.">                if (cert instanceof X509Certificate) {</span>
    +                    // avoid expired certificate
    +<span class="nc" id="L84">                    ((X509Certificate) cert).checkValidity();</span>
    +
    +                    //// SigUtils.checkCertificateUsage((X509Certificate) cert);
    +                }
    +            }
    +<span class="nc" id="L89">        }</span>
    +
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">        if (cert == null) {</span>
    +<span class="nc" id="L92">            throw new IOException(&quot;Could not find certificate&quot;);</span>
    +        }
    +<span class="nc" id="L94">    }</span>
    +
    +    public final void setPrivateKey(PrivateKey privateKey) {
    +<span class="nc" id="L97">        this.privateKey = privateKey;</span>
    +<span class="nc" id="L98">    }</span>
    +
    +    public Certificate[] getCertificateChain() {
    +<span class="nc" id="L101">        return certificateChain;</span>
    +    }
    +
    +    public final void setCertificateChain(final Certificate[] certificateChain) {
    +<span class="nc" id="L105">        this.certificateChain = certificateChain;</span>
    +<span class="nc" id="L106">    }</span>
    +
    +    public void setTsaUrl(String tsaUrl) {
    +<span class="nc" id="L109">        this.tsaUrl = tsaUrl;</span>
    +<span class="nc" id="L110">    }</span>
    +
    +    /**
    +     * SignatureInterface sample implementation.
    +     *
    +     * &lt;p&gt;This method will be called from inside of the pdfbox and create the PKCS #7 signature. The
    +     * given InputStream contains the bytes that are given by the byte range.
    +     *
    +     * &lt;p&gt;This method is for internal use only.
    +     *
    +     * &lt;p&gt;Use your favorite cryptographic library to implement PKCS #7 signature creation. If you
    +     * want to create the hash and the signature separately (e.g. to transfer only the hash to an
    +     * external application), read &lt;a href=&quot;https://stackoverflow.com/questions/41767351&quot;&gt;this
    +     * answer&lt;/a&gt; or &lt;a href=&quot;https://stackoverflow.com/questions/56867465&quot;&gt;this answer&lt;/a&gt;.
    +     *
    +     * @throws IOException
    +     */
    +    @Override
    +    public byte[] sign(InputStream content) throws IOException {
    +        // cannot be done private (interface)
    +        try {
    +<span class="nc" id="L131">            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();</span>
    +<span class="nc" id="L132">            X509Certificate cert = (X509Certificate) certificateChain[0];</span>
    +<span class="nc" id="L133">            ContentSigner sha1Signer =</span>
    +<span class="nc" id="L134">                    new JcaContentSignerBuilder(&quot;SHA256WithRSA&quot;).build(privateKey);</span>
    +<span class="nc" id="L135">            gen.addSignerInfoGenerator(</span>
    +                    new JcaSignerInfoGeneratorBuilder(
    +<span class="nc" id="L137">                                    new JcaDigestCalculatorProviderBuilder().build())</span>
    +<span class="nc" id="L138">                            .build(sha1Signer, cert));</span>
    +<span class="nc" id="L139">            gen.addCertificates(new JcaCertStore(Arrays.asList(certificateChain)));</span>
    +<span class="nc" id="L140">            CMSProcessableInputStream msg = new CMSProcessableInputStream(content);</span>
    +<span class="nc" id="L141">            CMSSignedData signedData = gen.generate(msg, false);</span>
    +<span class="nc bnc" id="L142" title="All 4 branches missed.">            if (tsaUrl != null &amp;&amp; !tsaUrl.isEmpty()) {</span>
    +<span class="nc" id="L143">                ValidationTimeStamp validation = new ValidationTimeStamp(tsaUrl);</span>
    +<span class="nc" id="L144">                signedData = validation.addSignedTimeStamp(signedData);</span>
    +            }
    +<span class="nc" id="L146">            return signedData.getEncoded();</span>
    +<span class="nc" id="L147">        } catch (GeneralSecurityException</span>
    +                | CMSException
    +                | OperatorCreationException
    +                | URISyntaxException e) {
    +<span class="nc" id="L151">            throw new IOException(e);</span>
    +        }
    +    }
    +
    +    public boolean isExternalSigning() {
    +<span class="nc" id="L156">        return externalSigning;</span>
    +    }
    +
    +    /**
    +     * Set if external signing scenario should be used. If {@code false}, SignatureInterface would
    +     * be used for signing.
    +     *
    +     * &lt;p&gt;Default: {@code false}
    +     *
    +     * @param externalSigning {@code true} if external signing should be performed
    +     */
    +    public void setExternalSigning(boolean externalSigning) {
    +<span class="nc" id="L168">        this.externalSigning = externalSigning;</span>
    +<span class="nc" id="L169">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/TSAClient.html b/testResult/html/org.apache.pdfbox.examples.signature/TSAClient.html
    new file mode 100644
    index 000000000..08563815c
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/TSAClient.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TSAClient</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">org.apache.pdfbox.examples.signature</a> &gt; <span class="el_class">TSAClient</span></div><h1>TSAClient</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">208 of 208</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">59</td><td class="ctr2">59</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="TSAClient.java.html#L127" class="el_method">getTSAResponse(byte[])</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="98" alt="98"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">29</td><td class="ctr2" id="i0">29</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="TSAClient.java.html#L81" class="el_method">getTimeStampToken(InputStream)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="101" height="10" title="83" alt="83"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">21</td><td class="ctr2" id="i1">21</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="TSAClient.java.html#L67" class="el_method">TSAClient(URL, String, String, MessageDigest)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="15" alt="15"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="TSAClient.java.html#L50" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="12" alt="12"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/TSAClient.java.html b/testResult/html/org.apache.pdfbox.examples.signature/TSAClient.java.html
    new file mode 100644
    index 000000000..5e94b8fa6
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/TSAClient.java.html
    @@ -0,0 +1,175 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TSAClient.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">org.apache.pdfbox.examples.signature</a> &gt; <span class="el_source">TSAClient.java</span></div><h1>TSAClient.java</h1><pre class="source lang-java linenums">/*
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    + * contributor license agreements.  See the NOTICE file distributed with
    + * this work for additional information regarding copyright ownership.
    + * The ASF licenses this file to You under the Apache License, Version 2.0
    + * (the &quot;License&quot;); you may not use this file except in compliance with
    + * the License.  You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package org.apache.pdfbox.examples.signature;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.OutputStream;
    +import java.math.BigInteger;
    +import java.net.URL;
    +import java.net.URLConnection;
    +import java.nio.charset.StandardCharsets;
    +import java.security.DigestInputStream;
    +import java.security.MessageDigest;
    +import java.security.SecureRandom;
    +import java.util.Base64;
    +import java.util.Random;
    +
    +import org.apache.logging.log4j.LogManager;
    +import org.apache.logging.log4j.Logger;
    +import org.bouncycastle.asn1.ASN1ObjectIdentifier;
    +import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
    +import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder;
    +import org.bouncycastle.tsp.TSPException;
    +import org.bouncycastle.tsp.TimeStampRequest;
    +import org.bouncycastle.tsp.TimeStampRequestGenerator;
    +import org.bouncycastle.tsp.TimeStampResponse;
    +import org.bouncycastle.tsp.TimeStampToken;
    +
    +/**
    + * Time Stamping Authority (TSA) Client [RFC 3161].
    + *
    + * @author Vakhtang Koroghlishvili
    + * @author John Hewson
    + */
    +public class TSAClient {
    +<span class="nc" id="L50">    private static final Logger LOG = LogManager.getLogger(TSAClient.class);</span>
    +
    +<span class="nc" id="L52">    private static final DigestAlgorithmIdentifierFinder ALGORITHM_OID_FINDER =</span>
    +            new DefaultDigestAlgorithmIdentifierFinder();
    +    // SecureRandom.getInstanceStrong() would be better, but sometimes blocks on Linux
    +<span class="nc" id="L55">    private static final Random RANDOM = new SecureRandom();</span>
    +    private final URL url;
    +    private final String username;
    +    private final String password;
    +    private final MessageDigest digest;
    +
    +    /**
    +     * @param url the URL of the TSA service
    +     * @param username user name of TSA
    +     * @param password password of TSA
    +     * @param digest the message digest to use
    +     */
    +<span class="nc" id="L67">    public TSAClient(URL url, String username, String password, MessageDigest digest) {</span>
    +<span class="nc" id="L68">        this.url = url;</span>
    +<span class="nc" id="L69">        this.username = username;</span>
    +<span class="nc" id="L70">        this.password = password;</span>
    +<span class="nc" id="L71">        this.digest = digest;</span>
    +<span class="nc" id="L72">    }</span>
    +
    +    /**
    +     * @param content
    +     * @return the time stamp token
    +     * @throws IOException if there was an error with the connection or data from the TSA server, or
    +     *     if the time stamp response could not be validated
    +     */
    +    public TimeStampToken getTimeStampToken(InputStream content) throws IOException {
    +<span class="nc" id="L81">        digest.reset();</span>
    +<span class="nc" id="L82">        DigestInputStream dis = new DigestInputStream(content, digest);</span>
    +<span class="nc bnc" id="L83" title="All 2 branches missed.">        while (dis.read() != -1) {</span>
    +            // do nothing
    +        }
    +<span class="nc" id="L86">        byte[] hash = digest.digest();</span>
    +
    +        // 32-bit cryptographic nonce
    +<span class="nc" id="L89">        int nonce = RANDOM.nextInt();</span>
    +
    +        // generate TSA request
    +<span class="nc" id="L92">        TimeStampRequestGenerator tsaGenerator = new TimeStampRequestGenerator();</span>
    +<span class="nc" id="L93">        tsaGenerator.setCertReq(true);</span>
    +<span class="nc" id="L94">        ASN1ObjectIdentifier oid = ALGORITHM_OID_FINDER.find(digest.getAlgorithm()).getAlgorithm();</span>
    +<span class="nc" id="L95">        TimeStampRequest request = tsaGenerator.generate(oid, hash, BigInteger.valueOf(nonce));</span>
    +
    +        // get TSA response
    +<span class="nc" id="L98">        byte[] tsaResponse = getTSAResponse(request.getEncoded());</span>
    +
    +        TimeStampResponse response;
    +        try {
    +<span class="nc" id="L102">            response = new TimeStampResponse(tsaResponse);</span>
    +<span class="nc" id="L103">            response.validate(request);</span>
    +<span class="nc" id="L104">        } catch (TSPException e) {</span>
    +<span class="nc" id="L105">            throw new IOException(e);</span>
    +<span class="nc" id="L106">        }</span>
    +
    +<span class="nc" id="L108">        TimeStampToken timeStampToken = response.getTimeStampToken();</span>
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">        if (timeStampToken == null) {</span>
    +            // https://www.ietf.org/rfc/rfc3161.html#section-2.4.2
    +<span class="nc" id="L111">            throw new IOException(</span>
    +                    &quot;Response from &quot;
    +                            + url
    +                            + &quot; does not have a time stamp token, status: &quot;
    +<span class="nc" id="L115">                            + response.getStatus()</span>
    +                            + &quot; (&quot;
    +<span class="nc" id="L117">                            + response.getStatusString()</span>
    +                            + &quot;)&quot;);
    +        }
    +
    +<span class="nc" id="L121">        return timeStampToken;</span>
    +    }
    +
    +    // gets response data for the given encoded TimeStampRequest data
    +    // throws IOException if a connection to the TSA cannot be established
    +    private byte[] getTSAResponse(byte[] request) throws IOException {
    +<span class="nc" id="L127">        LOG.debug(&quot;Opening connection to TSA server&quot;);</span>
    +
    +        // todo: support proxy servers
    +<span class="nc" id="L130">        URLConnection connection = url.openConnection();</span>
    +<span class="nc" id="L131">        connection.setDoOutput(true);</span>
    +<span class="nc" id="L132">        connection.setDoInput(true);</span>
    +<span class="nc" id="L133">        connection.setRequestProperty(&quot;Content-Type&quot;, &quot;application/timestamp-query&quot;);</span>
    +
    +<span class="nc" id="L135">        LOG.debug(&quot;Established connection to TSA server&quot;);</span>
    +
    +<span class="nc bnc" id="L137" title="All 8 branches missed.">        if (username != null &amp;&amp; password != null &amp;&amp; !username.isEmpty() &amp;&amp; !password.isEmpty()) {</span>
    +<span class="nc" id="L138">            String contentEncoding = connection.getContentEncoding();</span>
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">            if (contentEncoding == null) {</span>
    +<span class="nc" id="L140">                contentEncoding = StandardCharsets.UTF_8.name();</span>
    +            }
    +<span class="nc" id="L142">            connection.setRequestProperty(</span>
    +                    &quot;Authorization&quot;,
    +                    &quot;Basic &quot;
    +                            + new String(
    +<span class="nc" id="L146">                                    Base64.getEncoder()</span>
    +<span class="nc" id="L147">                                            .encode(</span>
    +                                                    (username + &quot;:&quot; + password)
    +<span class="nc" id="L149">                                                            .getBytes(contentEncoding))));</span>
    +        }
    +
    +        // read response
    +<span class="nc" id="L153">        try (OutputStream output = connection.getOutputStream()) {</span>
    +<span class="nc" id="L154">            output.write(request);</span>
    +<span class="nc" id="L155">        } catch (IOException ex) {</span>
    +<span class="nc" id="L156">            LOG.error(&quot;Exception when writing to {}&quot;, this.url, ex);</span>
    +<span class="nc" id="L157">            throw ex;</span>
    +<span class="nc" id="L158">        }</span>
    +
    +<span class="nc" id="L160">        LOG.debug(&quot;Waiting for response from TSA server&quot;);</span>
    +
    +        byte[] response;
    +<span class="nc" id="L163">        try (InputStream input = connection.getInputStream()) {</span>
    +<span class="nc" id="L164">            response = input.readAllBytes();</span>
    +<span class="nc" id="L165">        } catch (IOException ex) {</span>
    +<span class="nc" id="L166">            LOG.error(&quot;Exception when reading from {}&quot;, this.url, ex);</span>
    +<span class="nc" id="L167">            throw ex;</span>
    +<span class="nc" id="L168">        }</span>
    +
    +<span class="nc" id="L170">        LOG.debug(&quot;Received response from TSA server&quot;);</span>
    +
    +<span class="nc" id="L172">        return response;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/ValidationTimeStamp.html b/testResult/html/org.apache.pdfbox.examples.signature/ValidationTimeStamp.html
    new file mode 100644
    index 000000000..c71efb4d5
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/ValidationTimeStamp.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ValidationTimeStamp</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">org.apache.pdfbox.examples.signature</a> &gt; <span class="el_class">ValidationTimeStamp</span></div><h1>ValidationTimeStamp</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">112 of 112</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">26</td><td class="ctr2">26</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="ValidationTimeStamp.java.html#L111" class="el_method">signTimeStamp(SignerInformation)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="51" alt="51"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">13</td><td class="ctr2" id="i0">13</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ValidationTimeStamp.java.html#L89" class="el_method">addSignedTimeStamp(CMSSignedData)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="32" alt="32"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="ValidationTimeStamp.java.html#L62" class="el_method">ValidationTimeStamp(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="21" alt="21"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="ValidationTimeStamp.java.html#L77" class="el_method">getTimeStampToken(InputStream)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="8" alt="8"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/ValidationTimeStamp.java.html b/testResult/html/org.apache.pdfbox.examples.signature/ValidationTimeStamp.java.html
    new file mode 100644
    index 000000000..ab74d0f24
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/ValidationTimeStamp.java.html
    @@ -0,0 +1,135 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ValidationTimeStamp.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">org.apache.pdfbox.examples.signature</a> &gt; <span class="el_source">ValidationTimeStamp.java</span></div><h1>ValidationTimeStamp.java</h1><pre class="source lang-java linenums">/*
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    + * contributor license agreements.  See the NOTICE file distributed with
    + * this work for additional information regarding copyright ownership.
    + * The ASF licenses this file to You under the Apache License, Version 2.0
    + * (the &quot;License&quot;); you may not use this file except in compliance with
    + * the License.  You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package org.apache.pdfbox.examples.signature;
    +
    +import java.io.ByteArrayInputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.MalformedURLException;
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.security.MessageDigest;
    +import java.security.NoSuchAlgorithmException;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +import org.bouncycastle.asn1.ASN1Encodable;
    +import org.bouncycastle.asn1.ASN1EncodableVector;
    +import org.bouncycastle.asn1.ASN1ObjectIdentifier;
    +import org.bouncycastle.asn1.ASN1Primitive;
    +import org.bouncycastle.asn1.DERSet;
    +import org.bouncycastle.asn1.cms.Attribute;
    +import org.bouncycastle.asn1.cms.AttributeTable;
    +import org.bouncycastle.asn1.cms.Attributes;
    +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
    +import org.bouncycastle.cms.CMSSignedData;
    +import org.bouncycastle.cms.SignerInformation;
    +import org.bouncycastle.cms.SignerInformationStore;
    +import org.bouncycastle.tsp.TimeStampToken;
    +
    +/**
    + * This class wraps the TSAClient and the work that has to be done with it. Like Adding Signed
    + * TimeStamps to a signature, or creating a CMS timestamp attribute (with a signed timestamp)
    + *
    + * @author Others
    + * @author Alexis Suter
    + */
    +public class ValidationTimeStamp {
    +    private TSAClient tsaClient;
    +
    +    /**
    +     * @param tsaUrl The url where TS-Request will be done.
    +     * @throws NoSuchAlgorithmException
    +     * @throws MalformedURLException
    +     * @throws java.net.URISyntaxException
    +     */
    +    public ValidationTimeStamp(String tsaUrl)
    +<span class="nc" id="L62">            throws NoSuchAlgorithmException, MalformedURLException, URISyntaxException {</span>
    +<span class="nc bnc" id="L63" title="All 2 branches missed.">        if (tsaUrl != null) {</span>
    +<span class="nc" id="L64">            MessageDigest digest = MessageDigest.getInstance(&quot;SHA-256&quot;);</span>
    +<span class="nc" id="L65">            this.tsaClient = new TSAClient(new URI(tsaUrl).toURL(), null, null, digest);</span>
    +        }
    +<span class="nc" id="L67">    }</span>
    +
    +    /**
    +     * Creates a signed timestamp token by the given input stream.
    +     *
    +     * @param content InputStream of the content to sign
    +     * @return the byte[] of the timestamp token
    +     * @throws IOException
    +     */
    +    public byte[] getTimeStampToken(InputStream content) throws IOException {
    +<span class="nc" id="L77">        TimeStampToken timeStampToken = tsaClient.getTimeStampToken(content);</span>
    +<span class="nc" id="L78">        return timeStampToken.getEncoded();</span>
    +    }
    +
    +    /**
    +     * Extend cms signed data with TimeStamp first or to all signers
    +     *
    +     * @param signedData Generated CMS signed data
    +     * @return CMSSignedData Extended CMS signed data
    +     * @throws IOException
    +     */
    +    public CMSSignedData addSignedTimeStamp(CMSSignedData signedData) throws IOException {
    +<span class="nc" id="L89">        SignerInformationStore signerStore = signedData.getSignerInfos();</span>
    +<span class="nc" id="L90">        List&lt;SignerInformation&gt; newSigners = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">        for (SignerInformation signer : signerStore.getSigners()) {</span>
    +            // This adds a timestamp to every signer (into his unsigned attributes) in the
    +            // signature.
    +<span class="nc" id="L95">            newSigners.add(signTimeStamp(signer));</span>
    +<span class="nc" id="L96">        }</span>
    +
    +        // Because new SignerInformation is created, new SignerInfoStore has to be created
    +        // and also be replaced in signedData. Which creates a new signedData object.
    +<span class="nc" id="L100">        return CMSSignedData.replaceSigners(signedData, new SignerInformationStore(newSigners));</span>
    +    }
    +
    +    /**
    +     * Extend CMS Signer Information with the TimeStampToken into the unsigned Attributes.
    +     *
    +     * @param signer information about signer
    +     * @return information about SignerInformation
    +     * @throws IOException
    +     */
    +    private SignerInformation signTimeStamp(SignerInformation signer) throws IOException {
    +<span class="nc" id="L111">        AttributeTable unsignedAttributes = signer.getUnsignedAttributes();</span>
    +
    +<span class="nc" id="L113">        ASN1EncodableVector vector = new ASN1EncodableVector();</span>
    +<span class="nc bnc" id="L114" title="All 2 branches missed.">        if (unsignedAttributes != null) {</span>
    +<span class="nc" id="L115">            vector = unsignedAttributes.toASN1EncodableVector();</span>
    +        }
    +
    +<span class="nc" id="L118">        TimeStampToken timeStampToken =</span>
    +<span class="nc" id="L119">                tsaClient.getTimeStampToken(new ByteArrayInputStream(signer.getSignature()));</span>
    +<span class="nc" id="L120">        byte[] token = timeStampToken.getEncoded();</span>
    +<span class="nc" id="L121">        ASN1ObjectIdentifier oid = PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;</span>
    +<span class="nc" id="L122">        ASN1Encodable signatureTimeStamp =</span>
    +<span class="nc" id="L123">                new Attribute(oid, new DERSet(ASN1Primitive.fromByteArray(token)));</span>
    +
    +<span class="nc" id="L125">        vector.add(signatureTimeStamp);</span>
    +<span class="nc" id="L126">        Attributes signedAttributes = new Attributes(vector);</span>
    +
    +        // There is no other way changing the unsigned attributes of the signer information.
    +        // result is never null, new SignerInformation always returned,
    +        // see source code of replaceUnsignedAttributes
    +<span class="nc" id="L131">        return SignerInformation.replaceUnsignedAttributes(</span>
    +                signer, new AttributeTable(signedAttributes));
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/index.html b/testResult/html/org.apache.pdfbox.examples.signature/index.html
    new file mode 100644
    index 000000000..c63ba5677
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>org.apache.pdfbox.examples.signature</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">org.apache.pdfbox.examples.signature</span></div><h1>org.apache.pdfbox.examples.signature</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">500 of 500</td><td class="ctr2">0%</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="ctr1">38</td><td class="ctr2">38</td><td class="ctr1">138</td><td class="ctr2">138</td><td class="ctr1">21</td><td class="ctr2">21</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="TSAClient.html" class="el_class">TSAClient</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="208" alt="208"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">11</td><td class="ctr2" id="g1">11</td><td class="ctr1" id="h0">59</td><td class="ctr2" id="i0">59</td><td class="ctr1" id="j2">4</td><td class="ctr2" id="k2">4</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a1"><a href="CreateSignatureBase.html" class="el_class">CreateSignatureBase</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="84" height="10" title="147" alt="147"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">15</td><td class="ctr2" id="g0">15</td><td class="ctr1" id="h1">42</td><td class="ctr2" id="i1">42</td><td class="ctr1" id="j0">8</td><td class="ctr2" id="k0">8</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a3"><a href="ValidationTimeStamp.html" class="el_class">ValidationTimeStamp</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="112" alt="112"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">7</td><td class="ctr2" id="g2">7</td><td class="ctr1" id="h2">26</td><td class="ctr2" id="i2">26</td><td class="ctr1" id="j3">4</td><td class="ctr2" id="k3">4</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a0"><a href="CMSProcessableInputStream.html" class="el_class">CMSProcessableInputStream</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="33" alt="33"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">5</td><td class="ctr2" id="g3">5</td><td class="ctr1" id="h3">11</td><td class="ctr2" id="i3">11</td><td class="ctr1" id="j1">5</td><td class="ctr2" id="k1">5</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.signature/index.source.html b/testResult/html/org.apache.pdfbox.examples.signature/index.source.html
    new file mode 100644
    index 000000000..365ffb243
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.signature/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>org.apache.pdfbox.examples.signature</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">org.apache.pdfbox.examples.signature</span></div><h1>org.apache.pdfbox.examples.signature</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">500 of 500</td><td class="ctr2">0%</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="ctr1">38</td><td class="ctr2">38</td><td class="ctr1">138</td><td class="ctr2">138</td><td class="ctr1">21</td><td class="ctr2">21</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="TSAClient.java.html" class="el_source">TSAClient.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="208" alt="208"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">11</td><td class="ctr2" id="g1">11</td><td class="ctr1" id="h0">59</td><td class="ctr2" id="i0">59</td><td class="ctr1" id="j2">4</td><td class="ctr2" id="k2">4</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a1"><a href="CreateSignatureBase.java.html" class="el_source">CreateSignatureBase.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="84" height="10" title="147" alt="147"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">15</td><td class="ctr2" id="g0">15</td><td class="ctr1" id="h1">42</td><td class="ctr2" id="i1">42</td><td class="ctr1" id="j0">8</td><td class="ctr2" id="k0">8</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a3"><a href="ValidationTimeStamp.java.html" class="el_source">ValidationTimeStamp.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="112" alt="112"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">7</td><td class="ctr2" id="g2">7</td><td class="ctr1" id="h2">26</td><td class="ctr2" id="i2">26</td><td class="ctr1" id="j3">4</td><td class="ctr2" id="k3">4</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a0"><a href="CMSProcessableInputStream.java.html" class="el_source">CMSProcessableInputStream.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="33" alt="33"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">5</td><td class="ctr2" id="g3">5</td><td class="ctr1" id="h3">11</td><td class="ctr2" id="i3">11</td><td class="ctr1" id="j1">5</td><td class="ctr2" id="k1">5</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.util/ConnectedInputStream.html b/testResult/html/org.apache.pdfbox.examples.util/ConnectedInputStream.html
    new file mode 100644
    index 000000000..a06fa9ba8
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.util/ConnectedInputStream.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConnectedInputStream</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">org.apache.pdfbox.examples.util</a> &gt; <span class="el_class">ConnectedInputStream</span></div><h1>ConnectedInputStream</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">54 of 54</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">17</td><td class="ctr2">17</td><td class="ctr1">10</td><td class="ctr2">10</td></tr></tfoot><tbody><tr><td id="a2"><a href="ConnectedInputStream.java.html#L32" class="el_method">ConnectedInputStream(HttpURLConnection, InputStream)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a7"><a href="ConnectedInputStream.java.html#L49" class="el_method">read(byte[], int, int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="93" height="10" title="7" alt="7"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ConnectedInputStream.java.html#L79" class="el_method">close()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="93" height="10" title="7" alt="7"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="ConnectedInputStream.java.html#L44" class="el_method">read(byte[])</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="5" alt="5"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a9"><a href="ConnectedInputStream.java.html#L54" class="el_method">skip(long)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="5" alt="5"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="ConnectedInputStream.java.html#L64" class="el_method">mark(int)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="5" alt="5"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a5"><a href="ConnectedInputStream.java.html#L39" class="el_method">read()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a0"><a href="ConnectedInputStream.java.html#L59" class="el_method">available()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="ConnectedInputStream.java.html#L69" class="el_method">reset()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a4"><a href="ConnectedInputStream.java.html#L74" class="el_method">markSupported()</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.util/ConnectedInputStream.java.html b/testResult/html/org.apache.pdfbox.examples.util/ConnectedInputStream.java.html
    new file mode 100644
    index 000000000..9b06d99f6
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.util/ConnectedInputStream.java.html
    @@ -0,0 +1,83 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConnectedInputStream.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">org.apache.pdfbox.examples.util</a> &gt; <span class="el_source">ConnectedInputStream.java</span></div><h1>ConnectedInputStream.java</h1><pre class="source lang-java linenums">/*
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    + * contributor license agreements.  See the NOTICE file distributed with
    + * this work for additional information regarding copyright ownership.
    + * The ASF licenses this file to You under the Apache License, Version 2.0
    + * (the &quot;License&quot;); you may not use this file except in compliance with
    + * the License.  You may obtain a copy of the License at
    + *
    + *      http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package org.apache.pdfbox.examples.util;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.HttpURLConnection;
    +
    +/**
    + * Delegate class to close the connection when the class gets closed.
    + *
    + * @author Tilman Hausherr
    + */
    +public class ConnectedInputStream extends InputStream {
    +    HttpURLConnection con;
    +    InputStream is;
    +
    +<span class="nc" id="L32">    public ConnectedInputStream(HttpURLConnection con, InputStream is) {</span>
    +<span class="nc" id="L33">        this.con = con;</span>
    +<span class="nc" id="L34">        this.is = is;</span>
    +<span class="nc" id="L35">    }</span>
    +
    +    @Override
    +    public int read() throws IOException {
    +<span class="nc" id="L39">        return is.read();</span>
    +    }
    +
    +    @Override
    +    public int read(byte[] b) throws IOException {
    +<span class="nc" id="L44">        return is.read(b);</span>
    +    }
    +
    +    @Override
    +    public int read(byte[] b, int off, int len) throws IOException {
    +<span class="nc" id="L49">        return is.read(b, off, len);</span>
    +    }
    +
    +    @Override
    +    public long skip(long n) throws IOException {
    +<span class="nc" id="L54">        return is.skip(n);</span>
    +    }
    +
    +    @Override
    +    public int available() throws IOException {
    +<span class="nc" id="L59">        return is.available();</span>
    +    }
    +
    +    @Override
    +    public synchronized void mark(int readlimit) {
    +<span class="nc" id="L64">        is.mark(readlimit);</span>
    +<span class="nc" id="L65">    }</span>
    +
    +    @Override
    +    public synchronized void reset() throws IOException {
    +<span class="nc" id="L69">        is.reset();</span>
    +<span class="nc" id="L70">    }</span>
    +
    +    @Override
    +    public boolean markSupported() {
    +<span class="nc" id="L74">        return is.markSupported();</span>
    +    }
    +
    +    @Override
    +    public void close() throws IOException {
    +<span class="nc" id="L79">        is.close();</span>
    +<span class="nc" id="L80">        con.disconnect();</span>
    +<span class="nc" id="L81">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.util/DeletingRandomAccessFile.html b/testResult/html/org.apache.pdfbox.examples.util/DeletingRandomAccessFile.html
    new file mode 100644
    index 000000000..c6cfaf64f
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.util/DeletingRandomAccessFile.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DeletingRandomAccessFile</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">org.apache.pdfbox.examples.util</a> &gt; <span class="el_class">DeletingRandomAccessFile</span></div><h1>DeletingRandomAccessFile</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">40 of 40</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="DeletingRandomAccessFile.java.html#L25" class="el_method">close()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="28" alt="28"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">9</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="DeletingRandomAccessFile.java.html#L18" class="el_method">DeletingRandomAccessFile(File)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="DeletingRandomAccessFile.java.html#L13" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.util/DeletingRandomAccessFile.java.html b/testResult/html/org.apache.pdfbox.examples.util/DeletingRandomAccessFile.java.html
    new file mode 100644
    index 000000000..a55fa0b25
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.util/DeletingRandomAccessFile.java.html
    @@ -0,0 +1,40 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DeletingRandomAccessFile.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">org.apache.pdfbox.examples.util</a> &gt; <span class="el_source">DeletingRandomAccessFile.java</span></div><h1>DeletingRandomAccessFile.java</h1><pre class="source lang-java linenums">package org.apache.pdfbox.examples.util;
    +
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +
    +import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +/** A custom RandomAccessRead implementation that deletes the file when closed */
    +<span class="nc" id="L13">@Slf4j</span>
    +public class DeletingRandomAccessFile extends RandomAccessReadBufferedFile {
    +    private final Path tempFilePath;
    +
    +    public DeletingRandomAccessFile(File file) throws IOException {
    +<span class="nc" id="L18">        super(file);</span>
    +<span class="nc" id="L19">        this.tempFilePath = file.toPath();</span>
    +<span class="nc" id="L20">    }</span>
    +
    +    @Override
    +    public void close() throws IOException {
    +        try {
    +<span class="nc" id="L25">            super.close();</span>
    +        } finally {
    +            try {
    +<span class="nc" id="L28">                boolean deleted = Files.deleteIfExists(tempFilePath);</span>
    +<span class="nc bnc" id="L29" title="All 2 branches missed.">                if (deleted) {</span>
    +<span class="nc" id="L30">                    log.info(&quot;Successfully deleted temp file: {}&quot;, tempFilePath);</span>
    +                } else {
    +<span class="nc" id="L32">                    log.warn(&quot;Failed to delete temp file (may not exist): {}&quot;, tempFilePath);</span>
    +                }
    +<span class="nc" id="L34">            } catch (IOException e) {</span>
    +<span class="nc" id="L35">                log.error(&quot;Error deleting temp file: {}&quot;, tempFilePath, e);</span>
    +<span class="nc" id="L36">            }</span>
    +        }
    +<span class="nc" id="L38">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.util/index.html b/testResult/html/org.apache.pdfbox.examples.util/index.html
    new file mode 100644
    index 000000000..ffb25fc53
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.util/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>org.apache.pdfbox.examples.util</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">org.apache.pdfbox.examples.util</span></div><h1>org.apache.pdfbox.examples.util</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">94 of 94</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">30</td><td class="ctr2">30</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ConnectedInputStream.html" class="el_class">ConnectedInputStream</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="54" alt="54"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f0">10</td><td class="ctr2" id="g0">10</td><td class="ctr1" id="h0">17</td><td class="ctr2" id="i0">17</td><td class="ctr1" id="j0">10</td><td class="ctr2" id="k0">10</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a1"><a href="DeletingRandomAccessFile.html" class="el_class">DeletingRandomAccessFile</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="88" height="10" title="40" alt="40"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h1">13</td><td class="ctr2" id="i1">13</td><td class="ctr1" id="j1">3</td><td class="ctr2" id="k1">3</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/org.apache.pdfbox.examples.util/index.source.html b/testResult/html/org.apache.pdfbox.examples.util/index.source.html
    new file mode 100644
    index 000000000..71cb7f885
    --- /dev/null
    +++ b/testResult/html/org.apache.pdfbox.examples.util/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>org.apache.pdfbox.examples.util</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">org.apache.pdfbox.examples.util</span></div><h1>org.apache.pdfbox.examples.util</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">94 of 94</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">30</td><td class="ctr2">30</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ConnectedInputStream.java.html" class="el_source">ConnectedInputStream.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="54" alt="54"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f0">10</td><td class="ctr2" id="g0">10</td><td class="ctr1" id="h0">17</td><td class="ctr2" id="i0">17</td><td class="ctr1" id="j0">10</td><td class="ctr2" id="k0">10</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a1"><a href="DeletingRandomAccessFile.java.html" class="el_source">DeletingRandomAccessFile.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="88" height="10" title="40" alt="40"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h1">13</td><td class="ctr2" id="i1">13</td><td class="ctr1" id="j1">3</td><td class="ctr2" id="k1">3</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/EEAppConfig.html b/testResult/html/stirling.software.SPDF.EE/EEAppConfig.html
    new file mode 100644
    index 000000000..b69d497f1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/EEAppConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EEAppConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.EE</a> &gt; <span class="el_class">EEAppConfig</span></div><h1>EEAppConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">198 of 198</td><td class="ctr2">0%</td><td class="bar">62 of 62</td><td class="ctr2">0%</td><td class="ctr1">39</td><td class="ctr2">39</td><td class="ctr1">49</td><td class="ctr2">49</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a3"><a href="EEAppConfig.java.html#L60" class="el_method">migrateEnterpriseSettingsToPremium(ApplicationProperties)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="137" alt="137"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="54" alt="54"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">28</td><td class="ctr2" id="g0">28</td><td class="ctr1" id="h0">37</td><td class="ctr2" id="i0">37</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="EEAppConfig.java.html#L49" class="el_method">googleDriveEnabled()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="14" alt="14"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="EEAppConfig.java.html#L26" class="el_method">EEAppConfig(ApplicationProperties, LicenseKeyChecker)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="13" alt="13"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a5"><a href="EEAppConfig.java.html#L34" class="el_method">runningProOrHigher()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="9" alt="9"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="EEAppConfig.java.html#L39" class="el_method">runningEnterprise()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="9" alt="9"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="EEAppConfig.java.html#L44" class="el_method">ssoAutoLogin()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a1"><a href="EEAppConfig.java.html#L55" class="el_method">googleDriveConfig()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a7"><a href="EEAppConfig.java.html#L18" class="el_method">static {...}</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/EEAppConfig.java.html b/testResult/html/stirling.software.SPDF.EE/EEAppConfig.java.html
    new file mode 100644
    index 000000000..0d94b8b56
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/EEAppConfig.java.html
    @@ -0,0 +1,126 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EEAppConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.EE</a> &gt; <span class="el_source">EEAppConfig.java</span></div><h1>EEAppConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.EE;
    +
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.core.Ordered;
    +import org.springframework.core.annotation.Order;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.EE.KeygenLicenseVerifier.License;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.EnterpriseEdition;
    +import stirling.software.SPDF.model.ApplicationProperties.Premium;
    +import stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures.GoogleDrive;
    +
    +@Configuration
    +@Order(Ordered.HIGHEST_PRECEDENCE)
    +<span class="nc" id="L18">@Slf4j</span>
    +public class EEAppConfig {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    private final LicenseKeyChecker licenseKeyChecker;
    +
    +    public EEAppConfig(
    +<span class="nc" id="L26">            ApplicationProperties applicationProperties, LicenseKeyChecker licenseKeyChecker) {</span>
    +<span class="nc" id="L27">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L28">        this.licenseKeyChecker = licenseKeyChecker;</span>
    +<span class="nc" id="L29">        migrateEnterpriseSettingsToPremium(this.applicationProperties);</span>
    +<span class="nc" id="L30">    }</span>
    +
    +    @Bean(name = &quot;runningProOrHigher&quot;)
    +    public boolean runningProOrHigher() {
    +<span class="nc bnc" id="L34" title="All 2 branches missed.">        return licenseKeyChecker.getPremiumLicenseEnabledResult() != License.NORMAL;</span>
    +    }
    +
    +    @Bean(name = &quot;runningEE&quot;)
    +    public boolean runningEnterprise() {
    +<span class="nc bnc" id="L39" title="All 2 branches missed.">        return licenseKeyChecker.getPremiumLicenseEnabledResult() == License.ENTERPRISE;</span>
    +    }
    +
    +    @Bean(name = &quot;SSOAutoLogin&quot;)
    +    public boolean ssoAutoLogin() {
    +<span class="nc" id="L44">        return applicationProperties.getPremium().getProFeatures().isSsoAutoLogin();</span>
    +    }
    +
    +    @Bean(name = &quot;GoogleDriveEnabled&quot;)
    +    public boolean googleDriveEnabled() {
    +<span class="nc bnc" id="L49" title="All 2 branches missed.">        return runningProOrHigher()</span>
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">                &amp;&amp; applicationProperties.getPremium().getProFeatures().getGoogleDrive().isEnabled();</span>
    +    }
    +
    +    @Bean(name = &quot;GoogleDriveConfig&quot;)
    +    public GoogleDrive googleDriveConfig() {
    +<span class="nc" id="L55">        return applicationProperties.getPremium().getProFeatures().getGoogleDrive();</span>
    +    }
    +
    +    // TODO: Remove post migration
    +    public void migrateEnterpriseSettingsToPremium(ApplicationProperties applicationProperties) {
    +<span class="nc" id="L60">        EnterpriseEdition enterpriseEdition = applicationProperties.getEnterpriseEdition();</span>
    +<span class="nc" id="L61">        Premium premium = applicationProperties.getPremium();</span>
    +
    +        // Only proceed if both objects exist
    +<span class="nc bnc" id="L64" title="All 4 branches missed.">        if (enterpriseEdition == null || premium == null) {</span>
    +<span class="nc" id="L65">            return;</span>
    +        }
    +
    +        // Copy the license key if it's set in enterprise but not in premium
    +<span class="nc bnc" id="L69" title="All 2 branches missed.">        if (premium.getKey() == null</span>
    +<span class="nc bnc" id="L70" title="All 2 branches missed.">                || premium.getKey().equals(&quot;00000000-0000-0000-0000-000000000000&quot;)) {</span>
    +<span class="nc bnc" id="L71" title="All 2 branches missed.">            if (enterpriseEdition.getKey() != null</span>
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">                    &amp;&amp; !enterpriseEdition.getKey().equals(&quot;00000000-0000-0000-0000-000000000000&quot;)) {</span>
    +<span class="nc" id="L73">                premium.setKey(enterpriseEdition.getKey());</span>
    +            }
    +        }
    +
    +        // Copy enabled state if enterprise is enabled but premium is not
    +<span class="nc bnc" id="L78" title="All 4 branches missed.">        if (!premium.isEnabled() &amp;&amp; enterpriseEdition.isEnabled()) {</span>
    +<span class="nc" id="L79">            premium.setEnabled(true);</span>
    +        }
    +
    +        // Copy SSO auto login setting
    +<span class="nc bnc" id="L83" title="All 4 branches missed.">        if (!premium.getProFeatures().isSsoAutoLogin() &amp;&amp; enterpriseEdition.isSsoAutoLogin()) {</span>
    +<span class="nc" id="L84">            premium.getProFeatures().setSsoAutoLogin(true);</span>
    +        }
    +
    +        // Copy CustomMetadata settings
    +<span class="nc" id="L88">        Premium.ProFeatures.CustomMetadata premiumMetadata =</span>
    +<span class="nc" id="L89">                premium.getProFeatures().getCustomMetadata();</span>
    +<span class="nc" id="L90">        EnterpriseEdition.CustomMetadata enterpriseMetadata = enterpriseEdition.getCustomMetadata();</span>
    +
    +<span class="nc bnc" id="L92" title="All 4 branches missed.">        if (enterpriseMetadata != null &amp;&amp; premiumMetadata != null) {</span>
    +            // Copy autoUpdateMetadata setting
    +<span class="nc bnc" id="L94" title="All 2 branches missed.">            if (!premiumMetadata.isAutoUpdateMetadata()</span>
    +<span class="nc bnc" id="L95" title="All 2 branches missed.">                    &amp;&amp; enterpriseMetadata.isAutoUpdateMetadata()) {</span>
    +<span class="nc" id="L96">                premiumMetadata.setAutoUpdateMetadata(true);</span>
    +            }
    +
    +            // Copy author if not set in premium but set in enterprise
    +<span class="nc bnc" id="L100" title="All 2 branches missed.">            if ((premiumMetadata.getAuthor() == null</span>
    +<span class="nc bnc" id="L101" title="All 2 branches missed.">                            || premiumMetadata.getAuthor().trim().isEmpty()</span>
    +<span class="nc bnc" id="L102" title="All 2 branches missed.">                            || &quot;username&quot;.equals(premiumMetadata.getAuthor()))</span>
    +<span class="nc bnc" id="L103" title="All 2 branches missed.">                    &amp;&amp; enterpriseMetadata.getAuthor() != null</span>
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">                    &amp;&amp; !enterpriseMetadata.getAuthor().trim().isEmpty()) {</span>
    +<span class="nc" id="L105">                premiumMetadata.setAuthor(enterpriseMetadata.getAuthor());</span>
    +            }
    +
    +            // Copy creator if not set in premium but set in enterprise and different from default
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">            if ((premiumMetadata.getCreator() == null</span>
    +<span class="nc bnc" id="L110" title="All 2 branches missed.">                            || &quot;Stirling-PDF&quot;.equals(premiumMetadata.getCreator()))</span>
    +<span class="nc bnc" id="L111" title="All 2 branches missed.">                    &amp;&amp; enterpriseMetadata.getCreator() != null</span>
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">                    &amp;&amp; !&quot;Stirling-PDF&quot;.equals(enterpriseMetadata.getCreator())) {</span>
    +<span class="nc" id="L113">                premiumMetadata.setCreator(enterpriseMetadata.getCreator());</span>
    +            }
    +
    +            // Copy producer if not set in premium but set in enterprise and different from default
    +<span class="nc bnc" id="L117" title="All 2 branches missed.">            if ((premiumMetadata.getProducer() == null</span>
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">                            || &quot;Stirling-PDF&quot;.equals(premiumMetadata.getProducer()))</span>
    +<span class="nc bnc" id="L119" title="All 2 branches missed.">                    &amp;&amp; enterpriseMetadata.getProducer() != null</span>
    +<span class="nc bnc" id="L120" title="All 2 branches missed.">                    &amp;&amp; !&quot;Stirling-PDF&quot;.equals(enterpriseMetadata.getProducer())) {</span>
    +<span class="nc" id="L121">                premiumMetadata.setProducer(enterpriseMetadata.getProducer());</span>
    +            }
    +        }
    +<span class="nc" id="L124">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier$License.html b/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier$License.html
    new file mode 100644
    index 000000000..a23df4b41
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier$License.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeygenLicenseVerifier.License</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.EE</a> &gt; <span class="el_class">KeygenLicenseVerifier.License</span></div><h1>KeygenLicenseVerifier.License</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">21 of 21</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="KeygenLicenseVerifier.java.html#L29" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="21" alt="21"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier.html b/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier.html
    new file mode 100644
    index 000000000..e58a5e231
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeygenLicenseVerifier</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.EE</a> &gt; <span class="el_class">KeygenLicenseVerifier</span></div><h1>KeygenLicenseVerifier</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,039 of 1,039</td><td class="ctr2">0%</td><td class="bar">88 of 88</td><td class="ctr2">0%</td><td class="ctr1">60</td><td class="ctr2">60</td><td class="ctr1">290</td><td class="ctr2">290</td><td class="ctr1">16</td><td class="ctr2">16</td></tr></tfoot><tbody><tr><td id="a6"><a href="KeygenLicenseVerifier.java.html#L337" class="el_method">processJWTLicensePayload(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="177" alt="177"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="20" alt="20"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">11</td><td class="ctr2" id="g1">11</td><td class="ctr1" id="h0">49</td><td class="ctr2" id="i0">49</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="KeygenLicenseVerifier.java.html#L188" class="el_method">processCertificateData(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="91" height="10" title="135" alt="135"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">13</td><td class="ctr2" id="g0">13</td><td class="ctr1" id="h1">40</td><td class="ctr2" id="i1">40</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a10"><a href="KeygenLicenseVerifier.java.html#L85" class="el_method">verifyCertificateLicense(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="86" height="10" title="128" alt="128"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h2">38</td><td class="ctr2" id="i2">38</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a9"><a href="KeygenLicenseVerifier.java.html#L464" class="el_method">validateLicense(String, String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="112" alt="112"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h4">32</td><td class="ctr2" id="i4">32</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="KeygenLicenseVerifier.java.html#L515" class="el_method">activateMachine(String, String, String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="107" alt="107"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h3">34</td><td class="ctr2" id="i3">34</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a15"><a href="KeygenLicenseVerifier.java.html#L422" class="el_method">verifyStandardLicense(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="62" height="10" title="92" alt="92"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="16" alt="16"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f2">9</td><td class="ctr2" id="g2">9</td><td class="ctr1" id="h5">26</td><td class="ctr2" id="i5">26</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a12"><a href="KeygenLicenseVerifier.java.html#L268" class="el_method">verifyJWTLicense(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="76" alt="76"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h6">21</td><td class="ctr2" id="i6">21</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a11"><a href="KeygenLicenseVerifier.java.html#L153" class="el_method">verifyEd25519Signature(String, String)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="73" alt="73"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h7">18</td><td class="ctr2" id="i7">18</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a13"><a href="KeygenLicenseVerifier.java.html#L312" class="el_method">verifyJWTSignature(String, String)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="57" alt="57"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h8">13</td><td class="ctr2" id="i8">13</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a14"><a href="KeygenLicenseVerifier.java.html#L56" class="el_method">verifyLicense(String)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="38" alt="38"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h9">8</td><td class="ctr2" id="i9">8</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a2"><a href="KeygenLicenseVerifier.java.html#L76" class="el_method">isCertificateLicense(String)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="11" alt="11"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f6">3</td><td class="ctr2" id="g6">3</td><td class="ctr1" id="h13">1</td><td class="ctr2" id="i13">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a3"><a href="KeygenLicenseVerifier.java.html#L80" class="el_method">isJWTLicense(String)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="11" alt="11"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f7">3</td><td class="ctr2" id="g7">3</td><td class="ctr1" id="h14">1</td><td class="ctr2" id="i14">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a8"><a href="KeygenLicenseVerifier.java.html#L26" class="el_method">static {...}</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="8" alt="8"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h12">2</td><td class="ctr2" id="i12">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a4"><a href="KeygenLicenseVerifier.java.html#L51" class="el_method">KeygenLicenseVerifier(ApplicationProperties)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h10">3</td><td class="ctr2" id="i10">3</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a7"><a href="KeygenLicenseVerifier.java.html#L69" class="el_method">resultToEnum(boolean, KeygenLicenseVerifier.License)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f11">2</td><td class="ctr2" id="g11">2</td><td class="ctr1" id="h11">3</td><td class="ctr2" id="i11">3</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a1"><a href="KeygenLicenseVerifier.java.html#L579" class="el_method">generateMachineFingerprint()</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h15">1</td><td class="ctr2" id="i15">1</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier.java.html b/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier.java.html
    new file mode 100644
    index 000000000..4859b0b08
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/KeygenLicenseVerifier.java.html
    @@ -0,0 +1,582 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeygenLicenseVerifier.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.EE</a> &gt; <span class="el_source">KeygenLicenseVerifier.java</span></div><h1>KeygenLicenseVerifier.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.EE;
    +
    +import java.net.URI;
    +import java.net.http.HttpClient;
    +import java.net.http.HttpRequest;
    +import java.net.http.HttpResponse;
    +import java.util.Base64;
    +
    +import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
    +import org.bouncycastle.crypto.signers.Ed25519Signer;
    +import org.bouncycastle.util.encoders.Hex;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.stereotype.Service;
    +
    +import com.fasterxml.jackson.databind.JsonNode;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +import com.posthog.java.shaded.org.json.JSONException;
    +import com.posthog.java.shaded.org.json.JSONObject;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +
    +@Service
    +<span class="nc" id="L26">@Slf4j</span>
    +public class KeygenLicenseVerifier {
    +
    +<span class="nc" id="L29">    enum License {</span>
    +<span class="nc" id="L30">        NORMAL,</span>
    +<span class="nc" id="L31">        PRO,</span>
    +<span class="nc" id="L32">        ENTERPRISE</span>
    +    }
    +
    +    // License verification configuration
    +    private static final String ACCOUNT_ID = &quot;e5430f69-e834-4ae4-befd-b602aae5f372&quot;;
    +    private static final String BASE_URL = &quot;https://api.keygen.sh/v1/accounts&quot;;
    +
    +    private static final String PUBLIC_KEY =
    +            &quot;9fbc0d78593dcfcf03c945146edd60083bf5fae77dbc08aaa3935f03ce94a58d&quot;;
    +
    +    private static final String CERT_PREFIX = &quot;-----BEGIN LICENSE FILE-----&quot;;
    +    private static final String CERT_SUFFIX = &quot;-----END LICENSE FILE-----&quot;;
    +
    +    private static final String JWT_PREFIX = &quot;key/&quot;;
    +
    +<span class="nc" id="L47">    private static final ObjectMapper objectMapper = new ObjectMapper();</span>
    +    private final ApplicationProperties applicationProperties;
    +
    +    @Autowired
    +<span class="nc" id="L51">    public KeygenLicenseVerifier(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L52">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L53">    }</span>
    +
    +    public License verifyLicense(String licenseKeyOrCert) {
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">        if (isCertificateLicense(licenseKeyOrCert)) {</span>
    +<span class="nc" id="L57">            log.info(&quot;Detected certificate-based license. Processing...&quot;);</span>
    +<span class="nc" id="L58">            return resultToEnum(verifyCertificateLicense(licenseKeyOrCert), License.ENTERPRISE);</span>
    +<span class="nc bnc" id="L59" title="All 2 branches missed.">        } else if (isJWTLicense(licenseKeyOrCert)) {</span>
    +<span class="nc" id="L60">            log.info(&quot;Detected JWT-style license key. Processing...&quot;);</span>
    +<span class="nc" id="L61">            return resultToEnum(verifyJWTLicense(licenseKeyOrCert), License.ENTERPRISE);</span>
    +        } else {
    +<span class="nc" id="L63">            log.info(&quot;Detected standard license key. Processing...&quot;);</span>
    +<span class="nc" id="L64">            return resultToEnum(verifyStandardLicense(licenseKeyOrCert), License.PRO);</span>
    +        }
    +    }
    +
    +    private License resultToEnum(boolean result, License option) {
    +<span class="nc bnc" id="L69" title="All 2 branches missed.">        if (result) {</span>
    +<span class="nc" id="L70">            return option;</span>
    +        }
    +<span class="nc" id="L72">        return License.NORMAL;</span>
    +    }
    +
    +    private boolean isCertificateLicense(String license) {
    +<span class="nc bnc" id="L76" title="All 4 branches missed.">        return license != null &amp;&amp; license.trim().startsWith(CERT_PREFIX);</span>
    +    }
    +
    +    private boolean isJWTLicense(String license) {
    +<span class="nc bnc" id="L80" title="All 4 branches missed.">        return license != null &amp;&amp; license.trim().startsWith(JWT_PREFIX);</span>
    +    }
    +
    +    private boolean verifyCertificateLicense(String licenseFile) {
    +        try {
    +<span class="nc" id="L85">            log.info(&quot;Verifying certificate-based license&quot;);</span>
    +
    +<span class="nc" id="L87">            String encodedPayload = licenseFile;</span>
    +            // Remove the header
    +<span class="nc" id="L89">            encodedPayload = encodedPayload.replace(CERT_PREFIX, &quot;&quot;);</span>
    +            // Remove the footer
    +<span class="nc" id="L91">            encodedPayload = encodedPayload.replace(CERT_SUFFIX, &quot;&quot;);</span>
    +            // Remove all newlines
    +<span class="nc" id="L93">            encodedPayload = encodedPayload.replaceAll(&quot;\\r?\\n&quot;, &quot;&quot;);</span>
    +
    +<span class="nc" id="L95">            byte[] payloadBytes = Base64.getDecoder().decode(encodedPayload);</span>
    +<span class="nc" id="L96">            String payload = new String(payloadBytes);</span>
    +
    +<span class="nc" id="L98">            log.info(&quot;Decoded certificate payload: {}&quot;, payload);</span>
    +
    +<span class="nc" id="L100">            String encryptedData = &quot;&quot;;</span>
    +<span class="nc" id="L101">            String encodedSignature = &quot;&quot;;</span>
    +<span class="nc" id="L102">            String algorithm = &quot;&quot;;</span>
    +
    +            try {
    +<span class="nc" id="L105">                JSONObject attrs = new JSONObject(payload);</span>
    +<span class="nc" id="L106">                encryptedData = (String) attrs.get(&quot;enc&quot;);</span>
    +<span class="nc" id="L107">                encodedSignature = (String) attrs.get(&quot;sig&quot;);</span>
    +<span class="nc" id="L108">                algorithm = (String) attrs.get(&quot;alg&quot;);</span>
    +
    +<span class="nc" id="L110">                log.info(&quot;Certificate algorithm: {}&quot;, algorithm);</span>
    +<span class="nc" id="L111">            } catch (JSONException e) {</span>
    +<span class="nc" id="L112">                log.error(&quot;Failed to parse license file: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L113">                return false;</span>
    +<span class="nc" id="L114">            }</span>
    +
    +            // Verify license file algorithm
    +<span class="nc bnc" id="L117" title="All 2 branches missed.">            if (!algorithm.equals(&quot;base64+ed25519&quot;)) {</span>
    +<span class="nc" id="L118">                log.error(</span>
    +                        &quot;Unsupported algorithm: {}. Only base64+ed25519 is supported.&quot;, algorithm);
    +<span class="nc" id="L120">                return false;</span>
    +            }
    +
    +            // Verify signature
    +<span class="nc" id="L124">            boolean isSignatureValid = verifyEd25519Signature(encryptedData, encodedSignature);</span>
    +<span class="nc bnc" id="L125" title="All 2 branches missed.">            if (!isSignatureValid) {</span>
    +<span class="nc" id="L126">                log.error(&quot;License file signature is invalid&quot;);</span>
    +<span class="nc" id="L127">                return false;</span>
    +            }
    +
    +<span class="nc" id="L130">            log.info(&quot;License file signature is valid&quot;);</span>
    +
    +            // Decode the base64 data
    +            String decodedData;
    +            try {
    +<span class="nc" id="L135">                decodedData = new String(Base64.getDecoder().decode(encryptedData));</span>
    +<span class="nc" id="L136">            } catch (IllegalArgumentException e) {</span>
    +<span class="nc" id="L137">                log.error(&quot;Failed to decode license data: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L138">                return false;</span>
    +<span class="nc" id="L139">            }</span>
    +
    +            // Process the certificate data
    +<span class="nc" id="L142">            boolean isValid = processCertificateData(decodedData);</span>
    +
    +<span class="nc" id="L144">            return isValid;</span>
    +<span class="nc" id="L145">        } catch (Exception e) {</span>
    +<span class="nc" id="L146">            log.error(&quot;Error verifying certificate license: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L147">            return false;</span>
    +        }
    +    }
    +
    +    private boolean verifyEd25519Signature(String encryptedData, String encodedSignature) {
    +        try {
    +<span class="nc" id="L153">            log.info(&quot;Signature to verify: {}&quot;, encodedSignature);</span>
    +<span class="nc" id="L154">            log.info(&quot;Public key being used: {}&quot;, PUBLIC_KEY);</span>
    +
    +<span class="nc" id="L156">            byte[] signatureBytes = Base64.getDecoder().decode(encodedSignature);</span>
    +
    +            // Create the signing data format - prefix with &quot;license/&quot;
    +<span class="nc" id="L159">            String signingData = String.format(&quot;license/%s&quot;, encryptedData);</span>
    +<span class="nc" id="L160">            byte[] signingDataBytes = signingData.getBytes();</span>
    +
    +<span class="nc" id="L162">            log.info(&quot;Signing data length: {} bytes&quot;, signingDataBytes.length);</span>
    +
    +<span class="nc" id="L164">            byte[] publicKeyBytes = Hex.decode(PUBLIC_KEY);</span>
    +
    +<span class="nc" id="L166">            Ed25519PublicKeyParameters verifierParams =</span>
    +                    new Ed25519PublicKeyParameters(publicKeyBytes, 0);
    +<span class="nc" id="L168">            Ed25519Signer verifier = new Ed25519Signer();</span>
    +
    +<span class="nc" id="L170">            verifier.init(false, verifierParams);</span>
    +<span class="nc" id="L171">            verifier.update(signingDataBytes, 0, signingDataBytes.length);</span>
    +
    +            // Verify the signature
    +<span class="nc" id="L174">            boolean result = verifier.verifySignature(signatureBytes);</span>
    +<span class="nc bnc" id="L175" title="All 2 branches missed.">            if (!result) {</span>
    +<span class="nc" id="L176">                log.error(&quot;Signature verification failed with standard public key&quot;);</span>
    +            }
    +
    +<span class="nc" id="L179">            return result;</span>
    +<span class="nc" id="L180">        } catch (Exception e) {</span>
    +<span class="nc" id="L181">            log.error(&quot;Error verifying Ed25519 signature: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L182">            return false;</span>
    +        }
    +    }
    +
    +    private boolean processCertificateData(String certData) {
    +        try {
    +<span class="nc" id="L188">            log.info(&quot;Processing certificate data: {}&quot;, certData);</span>
    +
    +<span class="nc" id="L190">            JSONObject licenseData = new JSONObject(certData);</span>
    +<span class="nc" id="L191">            JSONObject metaObj = licenseData.optJSONObject(&quot;meta&quot;);</span>
    +<span class="nc bnc" id="L192" title="All 2 branches missed.">            if (metaObj != null) {</span>
    +<span class="nc" id="L193">                String issuedStr = metaObj.optString(&quot;issued&quot;, null);</span>
    +<span class="nc" id="L194">                String expiryStr = metaObj.optString(&quot;expiry&quot;, null);</span>
    +
    +<span class="nc bnc" id="L196" title="All 4 branches missed.">                if (issuedStr != null &amp;&amp; expiryStr != null) {</span>
    +<span class="nc" id="L197">                    java.time.Instant issued = java.time.Instant.parse(issuedStr);</span>
    +<span class="nc" id="L198">                    java.time.Instant expiry = java.time.Instant.parse(expiryStr);</span>
    +<span class="nc" id="L199">                    java.time.Instant now = java.time.Instant.now();</span>
    +
    +<span class="nc bnc" id="L201" title="All 2 branches missed.">                    if (issued.isAfter(now)) {</span>
    +<span class="nc" id="L202">                        log.error(</span>
    +                                &quot;License file issued date is in the future. Please adjust system time or request a new license&quot;);
    +<span class="nc" id="L204">                        return false;</span>
    +                    }
    +
    +                    // Check if the license file has expired
    +<span class="nc bnc" id="L208" title="All 2 branches missed.">                    if (expiry.isBefore(now)) {</span>
    +<span class="nc" id="L209">                        log.error(&quot;License file has expired on {}&quot;, expiryStr);</span>
    +<span class="nc" id="L210">                        return false;</span>
    +                    }
    +
    +<span class="nc" id="L213">                    log.info(&quot;License file valid until {}&quot;, expiryStr);</span>
    +                }
    +            }
    +
    +            // Get the main license data
    +<span class="nc" id="L218">            JSONObject dataObj = licenseData.optJSONObject(&quot;data&quot;);</span>
    +<span class="nc bnc" id="L219" title="All 2 branches missed.">            if (dataObj == null) {</span>
    +<span class="nc" id="L220">                log.error(&quot;No data object found in certificate&quot;);</span>
    +<span class="nc" id="L221">                return false;</span>
    +            }
    +
    +            // Extract license or machine information
    +<span class="nc" id="L225">            JSONObject attributesObj = dataObj.optJSONObject(&quot;attributes&quot;);</span>
    +<span class="nc bnc" id="L226" title="All 2 branches missed.">            if (attributesObj != null) {</span>
    +<span class="nc" id="L227">                log.info(&quot;Found attributes in certificate data&quot;);</span>
    +
    +                // Extract metadata
    +<span class="nc" id="L230">                JSONObject metadataObj = attributesObj.optJSONObject(&quot;metadata&quot;);</span>
    +<span class="nc bnc" id="L231" title="All 2 branches missed.">                if (metadataObj != null) {</span>
    +<span class="nc" id="L232">                    int users = metadataObj.optInt(&quot;users&quot;, 0);</span>
    +<span class="nc bnc" id="L233" title="All 2 branches missed.">                    if (users &gt; 0) {</span>
    +<span class="nc" id="L234">                        applicationProperties.getPremium().setMaxUsers(users);</span>
    +<span class="nc" id="L235">                        log.info(&quot;License allows for {} users&quot;, users);</span>
    +                    }
    +                }
    +
    +                // Check maxUsers directly in attributes if present from policy definition
    +                //                if (attributesObj.has(&quot;maxUsers&quot;)) {
    +                //                    int maxUsers = attributesObj.optInt(&quot;maxUsers&quot;, 0);
    +                //                    if (maxUsers &gt; 0) {
    +                //                        applicationProperties.getPremium().setMaxUsers(maxUsers);
    +                //                        log.info(&quot;License directly specifies {} max users&quot;,
    +                // maxUsers);
    +                //                    }
    +                //                }
    +
    +                // Check license status if available
    +<span class="nc" id="L250">                String status = attributesObj.optString(&quot;status&quot;, null);</span>
    +<span class="nc bnc" id="L251" title="All 2 branches missed.">                if (status != null</span>
    +<span class="nc bnc" id="L252" title="All 2 branches missed.">                        &amp;&amp; !status.equals(&quot;ACTIVE&quot;)</span>
    +<span class="nc bnc" id="L253" title="All 2 branches missed.">                        &amp;&amp; !status.equals(&quot;EXPIRING&quot;)) { // Accept &quot;EXPIRING&quot; status as valid</span>
    +<span class="nc" id="L254">                    log.error(&quot;License status is not active: {}&quot;, status);</span>
    +<span class="nc" id="L255">                    return false;</span>
    +                }
    +            }
    +
    +<span class="nc" id="L259">            return true;</span>
    +<span class="nc" id="L260">        } catch (Exception e) {</span>
    +<span class="nc" id="L261">            log.error(&quot;Error processing certificate data: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L262">            return false;</span>
    +        }
    +    }
    +
    +    private boolean verifyJWTLicense(String licenseKey) {
    +        try {
    +<span class="nc" id="L268">            log.info(&quot;Verifying ED25519_SIGN format license key&quot;);</span>
    +
    +            // Remove the &quot;key/&quot; prefix
    +<span class="nc" id="L271">            String licenseData = licenseKey.substring(JWT_PREFIX.length());</span>
    +
    +            // Split into payload and signature
    +<span class="nc" id="L274">            String[] parts = licenseData.split(&quot;\\.&quot;, 2);</span>
    +<span class="nc bnc" id="L275" title="All 2 branches missed.">            if (parts.length != 2) {</span>
    +<span class="nc" id="L276">                log.error(</span>
    +                        &quot;Invalid ED25519_SIGN license format. Expected format: key/payload.signature&quot;);
    +<span class="nc" id="L278">                return false;</span>
    +            }
    +
    +<span class="nc" id="L281">            String encodedPayload = parts[0];</span>
    +<span class="nc" id="L282">            String encodedSignature = parts[1];</span>
    +
    +            // Verify signature
    +<span class="nc" id="L285">            boolean isSignatureValid = verifyJWTSignature(encodedPayload, encodedSignature);</span>
    +<span class="nc bnc" id="L286" title="All 2 branches missed.">            if (!isSignatureValid) {</span>
    +<span class="nc" id="L287">                log.error(&quot;ED25519_SIGN license signature is invalid&quot;);</span>
    +<span class="nc" id="L288">                return false;</span>
    +            }
    +
    +<span class="nc" id="L291">            log.info(&quot;ED25519_SIGN license signature is valid&quot;);</span>
    +
    +            // Decode and process payload - first convert from URL-safe base64 if needed
    +<span class="nc" id="L294">            String base64Payload = encodedPayload.replace('-', '+').replace('_', '/');</span>
    +<span class="nc" id="L295">            byte[] payloadBytes = Base64.getDecoder().decode(base64Payload);</span>
    +<span class="nc" id="L296">            String payload = new String(payloadBytes);</span>
    +
    +            // Process the license payload
    +<span class="nc" id="L299">            boolean isValid = processJWTLicensePayload(payload);</span>
    +
    +<span class="nc" id="L301">            return isValid;</span>
    +<span class="nc" id="L302">        } catch (Exception e) {</span>
    +<span class="nc" id="L303">            log.error(&quot;Error verifying ED25519_SIGN license: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L304">            return false;</span>
    +        }
    +    }
    +
    +    private boolean verifyJWTSignature(String encodedPayload, String encodedSignature) {
    +        try {
    +            // Decode base64 signature
    +            byte[] signatureBytes =
    +<span class="nc" id="L312">                    Base64.getDecoder()</span>
    +<span class="nc" id="L313">                            .decode(encodedSignature.replace('-', '+').replace('_', '/'));</span>
    +
    +            // For ED25519_SIGN format, the signing data is &quot;key/&quot; + encodedPayload
    +<span class="nc" id="L316">            String signingData = String.format(&quot;key/%s&quot;, encodedPayload);</span>
    +<span class="nc" id="L317">            byte[] dataBytes = signingData.getBytes();</span>
    +
    +<span class="nc" id="L319">            byte[] publicKeyBytes = Hex.decode(PUBLIC_KEY);</span>
    +<span class="nc" id="L320">            Ed25519PublicKeyParameters verifierParams =</span>
    +                    new Ed25519PublicKeyParameters(publicKeyBytes, 0);
    +<span class="nc" id="L322">            Ed25519Signer verifier = new Ed25519Signer();</span>
    +
    +<span class="nc" id="L324">            verifier.init(false, verifierParams);</span>
    +<span class="nc" id="L325">            verifier.update(dataBytes, 0, dataBytes.length);</span>
    +
    +            // Verify the signature
    +<span class="nc" id="L328">            return verifier.verifySignature(signatureBytes);</span>
    +<span class="nc" id="L329">        } catch (Exception e) {</span>
    +<span class="nc" id="L330">            log.error(&quot;Error verifying JWT signature: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L331">            return false;</span>
    +        }
    +    }
    +
    +    private boolean processJWTLicensePayload(String payload) {
    +        try {
    +<span class="nc" id="L337">            log.info(&quot;Processing license payload: {}&quot;, payload);</span>
    +
    +<span class="nc" id="L339">            JSONObject licenseData = new JSONObject(payload);</span>
    +
    +<span class="nc" id="L341">            JSONObject licenseObj = licenseData.optJSONObject(&quot;license&quot;);</span>
    +<span class="nc bnc" id="L342" title="All 2 branches missed.">            if (licenseObj == null) {</span>
    +<span class="nc" id="L343">                String id = licenseData.optString(&quot;id&quot;, null);</span>
    +<span class="nc bnc" id="L344" title="All 2 branches missed.">                if (id != null) {</span>
    +<span class="nc" id="L345">                    log.info(&quot;Found license ID: {}&quot;, id);</span>
    +<span class="nc" id="L346">                    licenseObj = licenseData; // Use the root object as the license object</span>
    +                } else {
    +<span class="nc" id="L348">                    log.error(&quot;License data not found in payload&quot;);</span>
    +<span class="nc" id="L349">                    return false;</span>
    +                }
    +            }
    +
    +<span class="nc" id="L353">            String licenseId = licenseObj.optString(&quot;id&quot;, &quot;unknown&quot;);</span>
    +<span class="nc" id="L354">            log.info(&quot;Processing license with ID: {}&quot;, licenseId);</span>
    +
    +            // Check expiry date
    +<span class="nc" id="L357">            String expiryStr = licenseObj.optString(&quot;expiry&quot;, null);</span>
    +<span class="nc bnc" id="L358" title="All 4 branches missed.">            if (expiryStr != null &amp;&amp; !expiryStr.equals(&quot;null&quot;)) {</span>
    +<span class="nc" id="L359">                java.time.Instant expiry = java.time.Instant.parse(expiryStr);</span>
    +<span class="nc" id="L360">                java.time.Instant now = java.time.Instant.now();</span>
    +
    +<span class="nc bnc" id="L362" title="All 2 branches missed.">                if (now.isAfter(expiry)) {</span>
    +<span class="nc" id="L363">                    log.error(&quot;License has expired on {}&quot;, expiryStr);</span>
    +<span class="nc" id="L364">                    return false;</span>
    +                }
    +
    +<span class="nc" id="L367">                log.info(&quot;License valid until {}&quot;, expiryStr);</span>
    +<span class="nc" id="L368">            } else {</span>
    +<span class="nc" id="L369">                log.info(&quot;License has no expiration date&quot;);</span>
    +            }
    +
    +            // Extract account, product, policy info
    +<span class="nc" id="L373">            JSONObject accountObj = licenseData.optJSONObject(&quot;account&quot;);</span>
    +<span class="nc bnc" id="L374" title="All 2 branches missed.">            if (accountObj != null) {</span>
    +<span class="nc" id="L375">                String accountId = accountObj.optString(&quot;id&quot;, &quot;unknown&quot;);</span>
    +<span class="nc" id="L376">                log.info(&quot;License belongs to account: {}&quot;, accountId);</span>
    +
    +                // Verify this matches your expected account ID
    +<span class="nc bnc" id="L379" title="All 2 branches missed.">                if (!ACCOUNT_ID.equals(accountId)) {</span>
    +<span class="nc" id="L380">                    log.warn(&quot;License account ID does not match expected account ID&quot;);</span>
    +                    // You might want to fail verification here depending on your requirements
    +                }
    +            }
    +
    +            // Extract policy information if available
    +<span class="nc" id="L386">            JSONObject policyObj = licenseData.optJSONObject(&quot;policy&quot;);</span>
    +<span class="nc bnc" id="L387" title="All 2 branches missed.">            if (policyObj != null) {</span>
    +<span class="nc" id="L388">                String policyId = policyObj.optString(&quot;id&quot;, &quot;unknown&quot;);</span>
    +<span class="nc" id="L389">                log.info(&quot;License uses policy: {}&quot;, policyId);</span>
    +
    +                // Extract max users from policy if available (customize based on your policy
    +                // structure)
    +<span class="nc" id="L393">                int users = policyObj.optInt(&quot;users&quot;, 0);</span>
    +<span class="nc bnc" id="L394" title="All 2 branches missed.">                if (users &gt; 0) {</span>
    +<span class="nc" id="L395">                    applicationProperties.getPremium().setMaxUsers(users);</span>
    +<span class="nc" id="L396">                    log.info(&quot;License allows for {} users&quot;, users);</span>
    +                } else {
    +                    // Try to get users from metadata if present
    +<span class="nc" id="L399">                    Object metadataObj = policyObj.opt(&quot;metadata&quot;);</span>
    +<span class="nc bnc" id="L400" title="All 2 branches missed.">                    if (metadataObj instanceof JSONObject) {</span>
    +<span class="nc" id="L401">                        JSONObject metadata = (JSONObject) metadataObj;</span>
    +<span class="nc" id="L402">                        users = metadata.optInt(&quot;users&quot;, 1);</span>
    +<span class="nc" id="L403">                        applicationProperties.getPremium().setMaxUsers(users);</span>
    +<span class="nc" id="L404">                        log.info(&quot;License allows for {} users (from metadata)&quot;, users);</span>
    +<span class="nc" id="L405">                    } else {</span>
    +                        // Default value
    +<span class="nc" id="L407">                        applicationProperties.getPremium().setMaxUsers(1);</span>
    +<span class="nc" id="L408">                        log.info(&quot;Using default of 1 user for license&quot;);</span>
    +                    }
    +                }
    +            }
    +
    +<span class="nc" id="L413">            return true;</span>
    +<span class="nc" id="L414">        } catch (Exception e) {</span>
    +<span class="nc" id="L415">            log.error(&quot;Error processing license payload: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L416">            return false;</span>
    +        }
    +    }
    +
    +    private boolean verifyStandardLicense(String licenseKey) {
    +        try {
    +<span class="nc" id="L422">            log.info(&quot;Checking standard license key&quot;);</span>
    +<span class="nc" id="L423">            String machineFingerprint = generateMachineFingerprint();</span>
    +
    +            // First, try to validate the license
    +<span class="nc" id="L426">            JsonNode validationResponse = validateLicense(licenseKey, machineFingerprint);</span>
    +<span class="nc bnc" id="L427" title="All 2 branches missed.">            if (validationResponse != null) {</span>
    +<span class="nc" id="L428">                boolean isValid = validationResponse.path(&quot;meta&quot;).path(&quot;valid&quot;).asBoolean();</span>
    +<span class="nc" id="L429">                String licenseId = validationResponse.path(&quot;data&quot;).path(&quot;id&quot;).asText();</span>
    +<span class="nc bnc" id="L430" title="All 2 branches missed.">                if (!isValid) {</span>
    +<span class="nc" id="L431">                    String code = validationResponse.path(&quot;meta&quot;).path(&quot;code&quot;).asText();</span>
    +<span class="nc" id="L432">                    log.info(code);</span>
    +<span class="nc bnc" id="L433" title="All 2 branches missed.">                    if (&quot;NO_MACHINE&quot;.equals(code)</span>
    +<span class="nc bnc" id="L434" title="All 2 branches missed.">                            || &quot;NO_MACHINES&quot;.equals(code)</span>
    +<span class="nc bnc" id="L435" title="All 2 branches missed.">                            || &quot;FINGERPRINT_SCOPE_MISMATCH&quot;.equals(code)) {</span>
    +<span class="nc" id="L436">                        log.info(</span>
    +                                &quot;License not activated for this machine. Attempting to activate...&quot;);
    +<span class="nc" id="L438">                        boolean activated =</span>
    +<span class="nc" id="L439">                                activateMachine(licenseKey, licenseId, machineFingerprint);</span>
    +<span class="nc bnc" id="L440" title="All 2 branches missed.">                        if (activated) {</span>
    +                            // Revalidate after activation
    +<span class="nc" id="L442">                            validationResponse = validateLicense(licenseKey, machineFingerprint);</span>
    +<span class="nc bnc" id="L443" title="All 2 branches missed.">                            isValid =</span>
    +                                    validationResponse != null
    +                                            &amp;&amp; validationResponse
    +<span class="nc" id="L446">                                                    .path(&quot;meta&quot;)</span>
    +<span class="nc" id="L447">                                                    .path(&quot;valid&quot;)</span>
    +<span class="nc bnc" id="L448" title="All 2 branches missed.">                                                    .asBoolean();</span>
    +                        }
    +                    }
    +                }
    +<span class="nc" id="L452">                return isValid;</span>
    +            }
    +
    +<span class="nc" id="L455">            return false;</span>
    +<span class="nc" id="L456">        } catch (Exception e) {</span>
    +<span class="nc" id="L457">            log.error(&quot;Error verifying standard license: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L458">            return false;</span>
    +        }
    +    }
    +
    +    private JsonNode validateLicense(String licenseKey, String machineFingerprint)
    +            throws Exception {
    +<span class="nc" id="L464">        HttpClient client = HttpClient.newHttpClient();</span>
    +<span class="nc" id="L465">        String requestBody =</span>
    +<span class="nc" id="L466">                String.format(</span>
    +                        &quot;{\&quot;meta\&quot;:{\&quot;key\&quot;:\&quot;%s\&quot;,\&quot;scope\&quot;:{\&quot;fingerprint\&quot;:\&quot;%s\&quot;}}}&quot;,
    +                        licenseKey, machineFingerprint);
    +        HttpRequest request =
    +<span class="nc" id="L470">                HttpRequest.newBuilder()</span>
    +<span class="nc" id="L471">                        .uri(</span>
    +<span class="nc" id="L472">                                URI.create(</span>
    +                                        BASE_URL
    +                                                + &quot;/&quot;
    +                                                + ACCOUNT_ID
    +                                                + &quot;/licenses/actions/validate-key&quot;))
    +<span class="nc" id="L477">                        .header(&quot;Content-Type&quot;, &quot;application/vnd.api+json&quot;)</span>
    +<span class="nc" id="L478">                        .header(&quot;Accept&quot;, &quot;application/vnd.api+json&quot;)</span>
    +                        // .header(&quot;Authorization&quot;, &quot;License &quot; + licenseKey)
    +<span class="nc" id="L480">                        .POST(HttpRequest.BodyPublishers.ofString(requestBody))</span>
    +<span class="nc" id="L481">                        .build();</span>
    +
    +<span class="nc" id="L483">        HttpResponse&lt;String&gt; response = client.send(request, HttpResponse.BodyHandlers.ofString());</span>
    +<span class="nc" id="L484">        log.info(&quot;ValidateLicenseResponse body: {}&quot;, response.body());</span>
    +<span class="nc" id="L485">        JsonNode jsonResponse = objectMapper.readTree(response.body());</span>
    +<span class="nc bnc" id="L486" title="All 2 branches missed.">        if (response.statusCode() == 200) {</span>
    +<span class="nc" id="L487">            JsonNode metaNode = jsonResponse.path(&quot;meta&quot;);</span>
    +<span class="nc" id="L488">            boolean isValid = metaNode.path(&quot;valid&quot;).asBoolean();</span>
    +
    +<span class="nc" id="L490">            String detail = metaNode.path(&quot;detail&quot;).asText();</span>
    +<span class="nc" id="L491">            String code = metaNode.path(&quot;code&quot;).asText();</span>
    +
    +<span class="nc" id="L493">            log.info(&quot;License validity: &quot; + isValid);</span>
    +<span class="nc" id="L494">            log.info(&quot;Validation detail: &quot; + detail);</span>
    +<span class="nc" id="L495">            log.info(&quot;Validation code: &quot; + code);</span>
    +
    +<span class="nc" id="L497">            int users =</span>
    +                    jsonResponse
    +<span class="nc" id="L499">                            .path(&quot;data&quot;)</span>
    +<span class="nc" id="L500">                            .path(&quot;attributes&quot;)</span>
    +<span class="nc" id="L501">                            .path(&quot;metadata&quot;)</span>
    +<span class="nc" id="L502">                            .path(&quot;users&quot;)</span>
    +<span class="nc" id="L503">                            .asInt(0);</span>
    +<span class="nc" id="L504">            applicationProperties.getPremium().setMaxUsers(users);</span>
    +<span class="nc" id="L505">            log.info(applicationProperties.toString());</span>
    +
    +<span class="nc" id="L507">        } else {</span>
    +<span class="nc" id="L508">            log.error(&quot;Error validating license. Status code: {}&quot;, response.statusCode());</span>
    +        }
    +<span class="nc" id="L510">        return jsonResponse;</span>
    +    }
    +
    +    private boolean activateMachine(String licenseKey, String licenseId, String machineFingerprint)
    +            throws Exception {
    +<span class="nc" id="L515">        HttpClient client = HttpClient.newHttpClient();</span>
    +
    +        String hostname;
    +        try {
    +<span class="nc" id="L519">            hostname = java.net.InetAddress.getLocalHost().getHostName();</span>
    +<span class="nc" id="L520">        } catch (Exception e) {</span>
    +<span class="nc" id="L521">            hostname = &quot;Unknown&quot;;</span>
    +<span class="nc" id="L522">        }</span>
    +
    +<span class="nc" id="L524">        JSONObject body =</span>
    +                new JSONObject()
    +<span class="nc" id="L526">                        .put(</span>
    +                                &quot;data&quot;,
    +                                new JSONObject()
    +<span class="nc" id="L529">                                        .put(&quot;type&quot;, &quot;machines&quot;)</span>
    +<span class="nc" id="L530">                                        .put(</span>
    +                                                &quot;attributes&quot;,
    +                                                new JSONObject()
    +<span class="nc" id="L533">                                                        .put(&quot;fingerprint&quot;, machineFingerprint)</span>
    +<span class="nc" id="L534">                                                        .put(</span>
    +                                                                &quot;platform&quot;,
    +<span class="nc" id="L536">                                                                System.getProperty(&quot;os.name&quot;))</span>
    +<span class="nc" id="L537">                                                        .put(&quot;name&quot;, hostname))</span>
    +<span class="nc" id="L538">                                        .put(</span>
    +                                                &quot;relationships&quot;,
    +                                                new JSONObject()
    +<span class="nc" id="L541">                                                        .put(</span>
    +                                                                &quot;license&quot;,
    +                                                                new JSONObject()
    +<span class="nc" id="L544">                                                                        .put(</span>
    +                                                                                &quot;data&quot;,
    +                                                                                new JSONObject()
    +<span class="nc" id="L547">                                                                                        .put(</span>
    +                                                                                                &quot;type&quot;,
    +                                                                                                &quot;licenses&quot;)
    +<span class="nc" id="L550">                                                                                        .put(</span>
    +                                                                                                &quot;id&quot;,
    +                                                                                                licenseId)))));
    +
    +        HttpRequest request =
    +<span class="nc" id="L555">                HttpRequest.newBuilder()</span>
    +<span class="nc" id="L556">                        .uri(URI.create(BASE_URL + &quot;/&quot; + ACCOUNT_ID + &quot;/machines&quot;))</span>
    +<span class="nc" id="L557">                        .header(&quot;Content-Type&quot;, &quot;application/vnd.api+json&quot;)</span>
    +<span class="nc" id="L558">                        .header(&quot;Accept&quot;, &quot;application/vnd.api+json&quot;)</span>
    +<span class="nc" id="L559">                        .header(&quot;Authorization&quot;, &quot;License &quot; + licenseKey)</span>
    +<span class="nc" id="L560">                        .POST(HttpRequest.BodyPublishers.ofString(body.toString()))</span>
    +<span class="nc" id="L561">                        .build();</span>
    +
    +<span class="nc" id="L563">        HttpResponse&lt;String&gt; response = client.send(request, HttpResponse.BodyHandlers.ofString());</span>
    +<span class="nc" id="L564">        log.info(&quot;activateMachine Response body: &quot; + response.body());</span>
    +<span class="nc bnc" id="L565" title="All 2 branches missed.">        if (response.statusCode() == 201) {</span>
    +<span class="nc" id="L566">            log.info(&quot;Machine activated successfully&quot;);</span>
    +<span class="nc" id="L567">            return true;</span>
    +        } else {
    +<span class="nc" id="L569">            log.error(</span>
    +                    &quot;Error activating machine. Status code: {}, error: {}&quot;,
    +<span class="nc" id="L571">                    response.statusCode(),</span>
    +<span class="nc" id="L572">                    response.body());</span>
    +
    +<span class="nc" id="L574">            return false;</span>
    +        }
    +    }
    +
    +    private String generateMachineFingerprint() {
    +<span class="nc" id="L579">        return GeneralUtils.generateMachineFingerprint();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/LicenseKeyChecker.html b/testResult/html/stirling.software.SPDF.EE/LicenseKeyChecker.html
    new file mode 100644
    index 000000000..d7a003999
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/LicenseKeyChecker.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LicenseKeyChecker</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.EE</a> &gt; <span class="el_class">LicenseKeyChecker</span></div><h1>LicenseKeyChecker</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">139 of 139</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">42</td><td class="ctr2">42</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a2"><a href="LicenseKeyChecker.java.html#L65" class="el_method">getLicenseKeyContent(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="53" alt="53"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">15</td><td class="ctr2" id="i0">15</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="LicenseKeyChecker.java.html#L44" class="el_method">checkLicense()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="115" height="10" title="51" alt="51"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h1">13</td><td class="ctr2" id="i1">13</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="LicenseKeyChecker.java.html#L28" class="el_method">LicenseKeyChecker(KeygenLicenseVerifier, ApplicationProperties)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="14" alt="14"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="LicenseKeyChecker.java.html#L92" class="el_method">updateLicenseKey(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="11" alt="11"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="LicenseKeyChecker.java.html#L19" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="LicenseKeyChecker.java.html#L40" class="el_method">checkLicensePeriodically()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a3"><a href="LicenseKeyChecker.java.html#L98" class="el_method">getPremiumLicenseEnabledResult()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/LicenseKeyChecker.java.html b/testResult/html/stirling.software.SPDF.EE/LicenseKeyChecker.java.html
    new file mode 100644
    index 000000000..d655cec5f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/LicenseKeyChecker.java.html
    @@ -0,0 +1,101 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LicenseKeyChecker.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.EE</a> &gt; <span class="el_source">LicenseKeyChecker.java</span></div><h1>LicenseKeyChecker.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.EE;
    +
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.scheduling.annotation.Scheduled;
    +import org.springframework.stereotype.Component;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.EE.KeygenLicenseVerifier.License;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +
    +@Component
    +<span class="nc" id="L19">@Slf4j</span>
    +public class LicenseKeyChecker {
    +
    +    private static final String FILE_PREFIX = &quot;file:&quot;;
    +
    +    private final KeygenLicenseVerifier licenseService;
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L28">    private License premiumEnabledResult = License.NORMAL;</span>
    +
    +    @Autowired
    +    public LicenseKeyChecker(
    +<span class="nc" id="L32">            KeygenLicenseVerifier licenseService, ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L33">        this.licenseService = licenseService;</span>
    +<span class="nc" id="L34">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L35">        this.checkLicense();</span>
    +<span class="nc" id="L36">    }</span>
    +
    +    @Scheduled(initialDelay = 604800000, fixedRate = 604800000) // 7 days in milliseconds
    +    public void checkLicensePeriodically() {
    +<span class="nc" id="L40">        checkLicense();</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    private void checkLicense() {
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">        if (!applicationProperties.getPremium().isEnabled()) {</span>
    +<span class="nc" id="L45">            premiumEnabledResult = License.NORMAL;</span>
    +        } else {
    +<span class="nc" id="L47">            String licenseKey = getLicenseKeyContent(applicationProperties.getPremium().getKey());</span>
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">            if (licenseKey != null) {</span>
    +<span class="nc" id="L49">                premiumEnabledResult = licenseService.verifyLicense(licenseKey);</span>
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">                if (License.ENTERPRISE == premiumEnabledResult) {</span>
    +<span class="nc" id="L51">                    log.info(&quot;License key is Enterprise.&quot;);</span>
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">                } else if (License.PRO == premiumEnabledResult) {</span>
    +<span class="nc" id="L53">                    log.info(&quot;License key is Pro.&quot;);</span>
    +                } else {
    +<span class="nc" id="L55">                    log.info(&quot;License key is invalid, defaulting to non pro license.&quot;);</span>
    +                }
    +            } else {
    +<span class="nc" id="L58">                log.error(&quot;Failed to obtain license key content.&quot;);</span>
    +<span class="nc" id="L59">                premiumEnabledResult = License.NORMAL;</span>
    +            }
    +        }
    +<span class="nc" id="L62">    }</span>
    +
    +    private String getLicenseKeyContent(String keyOrFilePath) {
    +<span class="nc bnc" id="L65" title="All 4 branches missed.">        if (keyOrFilePath == null || keyOrFilePath.trim().isEmpty()) {</span>
    +<span class="nc" id="L66">            log.error(&quot;License key is not specified&quot;);</span>
    +<span class="nc" id="L67">            return null;</span>
    +        }
    +
    +        // Check if it's a file reference
    +<span class="nc bnc" id="L71" title="All 2 branches missed.">        if (keyOrFilePath.startsWith(FILE_PREFIX)) {</span>
    +<span class="nc" id="L72">            String filePath = keyOrFilePath.substring(FILE_PREFIX.length());</span>
    +            try {
    +<span class="nc" id="L74">                Path path = Paths.get(filePath);</span>
    +<span class="nc bnc" id="L75" title="All 2 branches missed.">                if (!Files.exists(path)) {</span>
    +<span class="nc" id="L76">                    log.error(&quot;License file does not exist: {}&quot;, filePath);</span>
    +<span class="nc" id="L77">                    return null;</span>
    +                }
    +<span class="nc" id="L79">                log.info(&quot;Reading license from file: {}&quot;, filePath);</span>
    +<span class="nc" id="L80">                return Files.readString(path);</span>
    +<span class="nc" id="L81">            } catch (IOException e) {</span>
    +<span class="nc" id="L82">                log.error(&quot;Failed to read license file: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L83">                return null;</span>
    +            }
    +        }
    +
    +        // It's a direct license key
    +<span class="nc" id="L88">        return keyOrFilePath;</span>
    +    }
    +
    +    public void updateLicenseKey(String newKey) throws IOException {
    +<span class="nc" id="L92">        applicationProperties.getPremium().setKey(newKey);</span>
    +<span class="nc" id="L93">        GeneralUtils.saveKeyToSettings(&quot;EnterpriseEdition.key&quot;, newKey);</span>
    +<span class="nc" id="L94">        checkLicense();</span>
    +<span class="nc" id="L95">    }</span>
    +
    +    public License getPremiumLicenseEnabledResult() {
    +<span class="nc" id="L98">        return premiumEnabledResult;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/index.html b/testResult/html/stirling.software.SPDF.EE/index.html
    new file mode 100644
    index 000000000..19ae86440
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.EE</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.EE</span></div><h1>stirling.software.SPDF.EE</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,397 of 1,397</td><td class="ctr2">0%</td><td class="bar">166 of 166</td><td class="ctr2">0%</td><td class="ctr1">115</td><td class="ctr2">115</td><td class="ctr1">385</td><td class="ctr2">385</td><td class="ctr1">32</td><td class="ctr2">32</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="KeygenLicenseVerifier.html" class="el_class">KeygenLicenseVerifier</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,039" alt="1,039"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="88" alt="88"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">60</td><td class="ctr2" id="g0">60</td><td class="ctr1" id="h0">290</td><td class="ctr2" id="i0">290</td><td class="ctr1" id="j0">16</td><td class="ctr2" id="k0">16</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="EEAppConfig.html" class="el_class">EEAppConfig</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="198" alt="198"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="84" height="10" title="62" alt="62"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">39</td><td class="ctr2" id="g1">39</td><td class="ctr1" id="h1">49</td><td class="ctr2" id="i1">49</td><td class="ctr1" id="j1">8</td><td class="ctr2" id="k1">8</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a3"><a href="LicenseKeyChecker.html" class="el_class">LicenseKeyChecker</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="139" alt="139"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="16" alt="16"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">15</td><td class="ctr2" id="g2">15</td><td class="ctr1" id="h2">42</td><td class="ctr2" id="i2">42</td><td class="ctr1" id="j2">7</td><td class="ctr2" id="k2">7</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a2"><a href="KeygenLicenseVerifier$License.html" class="el_class">KeygenLicenseVerifier.License</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="21" alt="21"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.EE/index.source.html b/testResult/html/stirling.software.SPDF.EE/index.source.html
    new file mode 100644
    index 000000000..b3cf43f8c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.EE/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.EE</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.EE</span></div><h1>stirling.software.SPDF.EE</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,397 of 1,397</td><td class="ctr2">0%</td><td class="bar">166 of 166</td><td class="ctr2">0%</td><td class="ctr1">115</td><td class="ctr2">115</td><td class="ctr1">385</td><td class="ctr2">385</td><td class="ctr1">32</td><td class="ctr2">32</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="KeygenLicenseVerifier.java.html" class="el_source">KeygenLicenseVerifier.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,060" alt="1,060"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="88" alt="88"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">61</td><td class="ctr2" id="g0">61</td><td class="ctr1" id="h0">294</td><td class="ctr2" id="i0">294</td><td class="ctr1" id="j0">17</td><td class="ctr2" id="k0">17</td><td class="ctr1" id="l0">2</td><td class="ctr2" id="m0">2</td></tr><tr><td id="a0"><a href="EEAppConfig.java.html" class="el_source">EEAppConfig.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="198" alt="198"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="84" height="10" title="62" alt="62"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">39</td><td class="ctr2" id="g1">39</td><td class="ctr1" id="h1">49</td><td class="ctr2" id="i1">49</td><td class="ctr1" id="j1">8</td><td class="ctr2" id="k1">8</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a2"><a href="LicenseKeyChecker.java.html" class="el_source">LicenseKeyChecker.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="139" alt="139"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="16" alt="16"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">15</td><td class="ctr2" id="g2">15</td><td class="ctr1" id="h2">42</td><td class="ctr2" id="i2">42</td><td class="ctr1" id="j2">7</td><td class="ctr2" id="k2">7</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.Factories/ReplaceAndInvertColorFactory.html b/testResult/html/stirling.software.SPDF.Factories/ReplaceAndInvertColorFactory.html
    new file mode 100644
    index 000000000..1331007c8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.Factories/ReplaceAndInvertColorFactory.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvertColorFactory</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.Factories</a> &gt; <span class="el_class">ReplaceAndInvertColorFactory</span></div><h1>ReplaceAndInvertColorFactory</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">29 of 29</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvertColorFactory.java.html#L22" class="el_method">replaceAndInvert(MultipartFile, ReplaceAndInvert, HighContrastColorCombination, String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="26" alt="26"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ReplaceAndInvertColorFactory.java.html#L13" class="el_method">ReplaceAndInvertColorFactory()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.Factories/ReplaceAndInvertColorFactory.java.html b/testResult/html/stirling.software.SPDF.Factories/ReplaceAndInvertColorFactory.java.html
    new file mode 100644
    index 000000000..f711dc932
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.Factories/ReplaceAndInvertColorFactory.java.html
    @@ -0,0 +1,40 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvertColorFactory.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.Factories</a> &gt; <span class="el_source">ReplaceAndInvertColorFactory.java</span></div><h1>ReplaceAndInvertColorFactory.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.Factories;
    +
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
    +import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
    +import stirling.software.SPDF.utils.misc.CustomColorReplaceStrategy;
    +import stirling.software.SPDF.utils.misc.InvertFullColorStrategy;
    +import stirling.software.SPDF.utils.misc.ReplaceAndInvertColorStrategy;
    +
    +@Component
    +<span class="nc" id="L13">public class ReplaceAndInvertColorFactory {</span>
    +
    +    public ReplaceAndInvertColorStrategy replaceAndInvert(
    +            MultipartFile file,
    +            ReplaceAndInvert replaceAndInvertOption,
    +            HighContrastColorCombination highContrastColorCombination,
    +            String backGroundColor,
    +            String textColor) {
    +
    +<span class="nc bnc" id="L22" title="All 4 branches missed.">        if (replaceAndInvertOption == ReplaceAndInvert.CUSTOM_COLOR</span>
    +                || replaceAndInvertOption == ReplaceAndInvert.HIGH_CONTRAST_COLOR) {
    +
    +<span class="nc" id="L25">            return new CustomColorReplaceStrategy(</span>
    +                    file,
    +                    replaceAndInvertOption,
    +                    textColor,
    +                    backGroundColor,
    +                    highContrastColorCombination);
    +
    +<span class="nc bnc" id="L32" title="All 2 branches missed.">        } else if (replaceAndInvertOption == ReplaceAndInvert.FULL_INVERSION) {</span>
    +
    +<span class="nc" id="L34">            return new InvertFullColorStrategy(file, replaceAndInvertOption);</span>
    +        }
    +
    +<span class="nc" id="L37">        return null;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.Factories/index.html b/testResult/html/stirling.software.SPDF.Factories/index.html
    new file mode 100644
    index 000000000..10faa5fa7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.Factories/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.Factories</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.Factories</span></div><h1>stirling.software.SPDF.Factories</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">29 of 29</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvertColorFactory.html" class="el_class">ReplaceAndInvertColorFactory</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="29" alt="29"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">2</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.Factories/index.source.html b/testResult/html/stirling.software.SPDF.Factories/index.source.html
    new file mode 100644
    index 000000000..9d7dd86e0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.Factories/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.Factories</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.Factories</span></div><h1>stirling.software.SPDF.Factories</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">29 of 29</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvertColorFactory.java.html" class="el_source">ReplaceAndInvertColorFactory.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="29" alt="29"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">2</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$1.html b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$1.html
    new file mode 100644
    index 000000000..500e032a8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DesktopBrowser.new MavenCefAppHandlerAdapter() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_class">DesktopBrowser.new MavenCefAppHandlerAdapter() {...}</span></div><h1>DesktopBrowser.new MavenCefAppHandlerAdapter() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">17 of 17</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="DesktopBrowser.java.html#L139" class="el_method">stateHasChanged(CefApp.CefAppState)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="11" alt="11"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="DesktopBrowser.java.html#L136" class="el_method">{...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="65" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$2.html b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$2.html
    new file mode 100644
    index 000000000..ab00b8d29
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$2.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DesktopBrowser.new CefDownloadHandlerAdapter() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_class">DesktopBrowser.new CefDownloadHandlerAdapter() {...}</span></div><h1>DesktopBrowser.new CefDownloadHandlerAdapter() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">30 of 30</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="DesktopBrowser.java.html#L165" class="el_method">onDownloadUpdated(CefBrowser, CefDownloadItem, CefDownloadItemCallback)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="DesktopBrowser.java.html#L149" class="el_method">{...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="DesktopBrowser.java.html#L156" class="el_method">onBeforeDownload(CefBrowser, CefDownloadItem, String, CefBeforeDownloadCallback)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$3.html b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$3.html
    new file mode 100644
    index 000000000..9ee0aecee
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$3.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DesktopBrowser.new ConsoleProgressHandler() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_class">DesktopBrowser.new ConsoleProgressHandler() {...}</span></div><h1>DesktopBrowser.new ConsoleProgressHandler() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">70 of 70</td><td class="ctr2">0%</td><td class="bar">10 of 10</td><td class="ctr2">0%</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">23</td><td class="ctr2">23</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="DesktopBrowser.java.html#L181" class="el_method">lambda$handleProgress$0(EnumProgress, float)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="55" alt="55"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h0">19</td><td class="ctr2" id="i0">19</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="DesktopBrowser.java.html#L178" class="el_method">handleProgress(EnumProgress, float)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="DesktopBrowser.java.html#L175" class="el_method">{...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$4.html b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$4.html
    new file mode 100644
    index 000000000..3bdfd7afd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$4.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DesktopBrowser.new WindowAdapter() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_class">DesktopBrowser.new WindowAdapter() {...}</span></div><h1>DesktopBrowser.new WindowAdapter() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="DesktopBrowser.java.html#L227" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="DesktopBrowser.java.html#L230" class="el_method">windowClosing(WindowEvent)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$5.html b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$5.html
    new file mode 100644
    index 000000000..0840b9dd8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$5.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DesktopBrowser.new CefLoadHandlerAdapter() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_class">DesktopBrowser.new CefLoadHandlerAdapter() {...}</span></div><h1>DesktopBrowser.new CefLoadHandlerAdapter() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">150 of 150</td><td class="ctr2">0%</td><td class="bar">10 of 10</td><td class="ctr2">0%</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">52</td><td class="ctr2">52</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="DesktopBrowser.java.html#L270" class="el_method">lambda$onLoadingStateChange$1(CefBrowser)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="82" alt="82"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">33</td><td class="ctr2" id="i0">33</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="DesktopBrowser.java.html#L253" class="el_method">onLoadingStateChange(CefBrowser, boolean, boolean, boolean)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="67" height="10" title="46" alt="46"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">11</td><td class="ctr2" id="i1">11</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="DesktopBrowser.java.html#L303" class="el_method">lambda$onLoadingStateChange$0(CefBrowser, ActionEvent)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="13" alt="13"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">7</td><td class="ctr2" id="i2">7</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="DesktopBrowser.java.html#L246" class="el_method">{...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="9" alt="9"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$6.html b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$6.html
    new file mode 100644
    index 000000000..fd871681c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser$6.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DesktopBrowser.new WindowStateListener() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_class">DesktopBrowser.new WindowStateListener() {...}</span></div><h1>DesktopBrowser.new WindowStateListener() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="DesktopBrowser.java.html#L386" class="el_method">windowStateChanged(WindowEvent)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="DesktopBrowser.java.html#L384" class="el_method">{...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser.html b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser.html
    new file mode 100644
    index 000000000..6ea11940f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DesktopBrowser</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_class">DesktopBrowser</span></div><h1>DesktopBrowser</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">498 of 498</td><td class="ctr2">0%</td><td class="bar">30 of 30</td><td class="ctr2">0%</td><td class="ctr1">35</td><td class="ctr2">35</td><td class="ctr1">161</td><td class="ctr2">161</td><td class="ctr1">20</td><td class="ctr2">20</td></tr></tfoot><tbody><tr><td id="a4"><a href="DesktopBrowser.java.html#L437" class="el_method">forceInitializeUI()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="89" alt="89"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h0">34</td><td class="ctr2" id="i0">34</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a14"><a href="DesktopBrowser.java.html#L399" class="el_method">loadIcon()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="93" height="10" title="69" alt="69"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h2">20</td><td class="ctr2" id="i2">20</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a18"><a href="DesktopBrowser.java.html#L337" class="el_method">setupTrayIcon(Image)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="91" height="10" title="68" alt="68"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h1">21</td><td class="ctr2" id="i1">21</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a17"><a href="DesktopBrowser.java.html#L216" class="el_method">setupMainFrame()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="67" height="10" title="50" alt="50"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h4">13</td><td class="ctr2" id="i4">13</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="DesktopBrowser.java.html#L120" class="el_method">configureCefSettings(CefAppBuilder)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="49" alt="49"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h5">11</td><td class="ctr2" id="i5">11</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a9"><a href="DesktopBrowser.java.html#L77" class="el_method">lambda$initWebUI$3(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="38" alt="38"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h3">14</td><td class="ctr2" id="i3">14</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a8"><a href="DesktopBrowser.java.html#L92" class="el_method">lambda$initWebUI$2(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="22" alt="22"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h6">7</td><td class="ctr2" id="i6">7</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a0"><a href="DesktopBrowser.java.html#L429" class="el_method">cleanup()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="18" alt="18"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h8">5</td><td class="ctr2" id="i8">5</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a16"><a href="DesktopBrowser.java.html#L242" class="el_method">setupLoadHandler()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="16" alt="16"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">4</td><td class="ctr2" id="i9">4</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a6"><a href="DesktopBrowser.java.html#L469" class="el_method">lambda$forceInitializeUI$7(ActionEvent)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="13" alt="13"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h7">6</td><td class="ctr2" id="i7">6</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a10"><a href="DesktopBrowser.java.html#L68" class="el_method">lambda$new$0()</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="10" alt="10"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">3</td><td class="ctr2" id="i11">3</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a15"><a href="DesktopBrowser.java.html#L148" class="el_method">setupDownloadHandler()</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="8" alt="8"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h16">2</td><td class="ctr2" id="i16">2</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a13"><a href="DesktopBrowser.java.html#L375" class="el_method">lambda$setupTrayIcon$6(ActionEvent)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="7" alt="7"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h12">3</td><td class="ctr2" id="i12">3</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a11"><a href="DesktopBrowser.java.html#L352" class="el_method">lambda$setupTrayIcon$4(ActionEvent)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="7" alt="7"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h13">3</td><td class="ctr2" id="i13">3</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a7"><a href="DesktopBrowser.java.html#L101" class="el_method">lambda$initWebUI$1(ActionEvent)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="7" alt="7"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h10">4</td><td class="ctr2" id="i10">4</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a5"><a href="DesktopBrowser.java.html#L74" class="el_method">initWebUI(String)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="6" alt="6"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h17">2</td><td class="ctr2" id="i17">2</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a19"><a href="DesktopBrowser.java.html#L50" class="el_method">static {...}</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="6" alt="6"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h18">2</td><td class="ctr2" id="i18">2</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a3"><a href="DesktopBrowser.java.html#L65" class="el_method">DesktopBrowser()</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="5" alt="5"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h14">3</td><td class="ctr2" id="i14">3</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a2"><a href="DesktopBrowser.java.html#L175" class="el_method">createProgressHandler()</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="5" alt="5"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h19">1</td><td class="ctr2" id="i19">1</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a12"><a href="DesktopBrowser.java.html#L359" class="el_method">lambda$setupTrayIcon$5(ActionEvent)</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="5" alt="5"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h15">3</td><td class="ctr2" id="i15">3</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser.java.html b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser.java.html
    new file mode 100644
    index 000000000..b5a83761a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/DesktopBrowser.java.html
    @@ -0,0 +1,498 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DesktopBrowser.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_source">DesktopBrowser.java</span></div><h1>DesktopBrowser.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.UI.impl;
    +
    +import java.awt.AWTException;
    +import java.awt.BorderLayout;
    +import java.awt.Frame;
    +import java.awt.Image;
    +import java.awt.MenuItem;
    +import java.awt.PopupMenu;
    +import java.awt.SystemTray;
    +import java.awt.TrayIcon;
    +import java.awt.event.WindowEvent;
    +import java.awt.event.WindowStateListener;
    +import java.io.File;
    +import java.io.InputStream;
    +import java.util.Objects;
    +import java.util.concurrent.CompletableFuture;
    +
    +import javax.imageio.ImageIO;
    +import javax.swing.JFrame;
    +import javax.swing.JPanel;
    +import javax.swing.SwingUtilities;
    +import javax.swing.Timer;
    +
    +import org.cef.CefApp;
    +import org.cef.CefClient;
    +import org.cef.CefSettings;
    +import org.cef.browser.CefBrowser;
    +import org.cef.callback.CefBeforeDownloadCallback;
    +import org.cef.callback.CefDownloadItem;
    +import org.cef.callback.CefDownloadItemCallback;
    +import org.cef.handler.CefDownloadHandlerAdapter;
    +import org.cef.handler.CefLoadHandlerAdapter;
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    +import org.springframework.stereotype.Component;
    +
    +import jakarta.annotation.PreDestroy;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import me.friwi.jcefmaven.CefAppBuilder;
    +import me.friwi.jcefmaven.EnumProgress;
    +import me.friwi.jcefmaven.MavenCefAppHandlerAdapter;
    +import me.friwi.jcefmaven.impl.progress.ConsoleProgressHandler;
    +
    +import stirling.software.SPDF.UI.WebBrowser;
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.utils.UIScaling;
    +
    +@Component
    +<span class="nc" id="L50">@Slf4j</span>
    +@ConditionalOnProperty(
    +        name = &quot;STIRLING_PDF_DESKTOP_UI&quot;,
    +        havingValue = &quot;true&quot;,
    +        matchIfMissing = false)
    +public class DesktopBrowser implements WebBrowser {
    +    private static CefApp cefApp;
    +    private static CefClient client;
    +    private static CefBrowser browser;
    +    private static JFrame frame;
    +    private static LoadingWindow loadingWindow;
    +<span class="nc" id="L61">    private static volatile boolean browserInitialized = false;</span>
    +    private static TrayIcon trayIcon;
    +    private static SystemTray systemTray;
    +
    +<span class="nc" id="L65">    public DesktopBrowser() {</span>
    +<span class="nc" id="L66">        SwingUtilities.invokeLater(</span>
    +                () -&gt; {
    +<span class="nc" id="L68">                    loadingWindow = new LoadingWindow(null, &quot;Initializing...&quot;);</span>
    +<span class="nc" id="L69">                    loadingWindow.setVisible(true);</span>
    +<span class="nc" id="L70">                });</span>
    +<span class="nc" id="L71">    }</span>
    +
    +    public void initWebUI(String url) {
    +<span class="nc" id="L74">        CompletableFuture.runAsync(</span>
    +                () -&gt; {
    +                    try {
    +<span class="nc" id="L77">                        CefAppBuilder builder = new CefAppBuilder();</span>
    +<span class="nc" id="L78">                        configureCefSettings(builder);</span>
    +<span class="nc" id="L79">                        builder.setProgressHandler(createProgressHandler());</span>
    +<span class="nc" id="L80">                        builder.setInstallDir(</span>
    +<span class="nc" id="L81">                                new File(InstallationPathConfig.getClientWebUIPath()));</span>
    +                        // Build and initialize CEF
    +<span class="nc" id="L83">                        cefApp = builder.build();</span>
    +<span class="nc" id="L84">                        client = cefApp.createClient();</span>
    +
    +                        // Set up download handler
    +<span class="nc" id="L87">                        setupDownloadHandler();</span>
    +
    +                        // Create browser and frame on EDT
    +<span class="nc" id="L90">                        SwingUtilities.invokeAndWait(</span>
    +                                () -&gt; {
    +<span class="nc" id="L92">                                    browser = client.createBrowser(url, false, false);</span>
    +<span class="nc" id="L93">                                    setupMainFrame();</span>
    +<span class="nc" id="L94">                                    setupLoadHandler();</span>
    +
    +                                    // Force initialize UI after 7 seconds if not already done
    +<span class="nc" id="L97">                                    Timer timeoutTimer =</span>
    +                                            new Timer(
    +                                                    2500,
    +                                                    e -&gt; {
    +<span class="nc" id="L101">                                                        log.warn(</span>
    +                                                                &quot;Loading timeout reached. Forcing&quot;
    +                                                                        + &quot; UI transition.&quot;);
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">                                                        if (!browserInitialized) {</span>
    +                                                            // Force UI initialization
    +<span class="nc" id="L106">                                                            forceInitializeUI();</span>
    +                                                        }
    +<span class="nc" id="L108">                                                    });</span>
    +<span class="nc" id="L109">                                    timeoutTimer.setRepeats(false);</span>
    +<span class="nc" id="L110">                                    timeoutTimer.start();</span>
    +<span class="nc" id="L111">                                });</span>
    +<span class="nc" id="L112">                    } catch (Exception e) {</span>
    +<span class="nc" id="L113">                        log.error(&quot;Error initializing JCEF browser: &quot;, e);</span>
    +<span class="nc" id="L114">                        cleanup();</span>
    +<span class="nc" id="L115">                    }</span>
    +<span class="nc" id="L116">                });</span>
    +<span class="nc" id="L117">    }</span>
    +
    +    private void configureCefSettings(CefAppBuilder builder) {
    +<span class="nc" id="L120">        CefSettings settings = builder.getCefSettings();</span>
    +<span class="nc" id="L121">        String basePath = InstallationPathConfig.getClientWebUIPath();</span>
    +<span class="nc" id="L122">        log.info(&quot;basePath &quot; + basePath);</span>
    +<span class="nc" id="L123">        settings.cache_path = new File(basePath + &quot;cache&quot;).getAbsolutePath();</span>
    +<span class="nc" id="L124">        settings.root_cache_path = new File(basePath + &quot;root_cache&quot;).getAbsolutePath();</span>
    +        //        settings.browser_subprocess_path = new File(basePath +
    +        // &quot;subprocess&quot;).getAbsolutePath();
    +        //        settings.resources_dir_path = new File(basePath + &quot;resources&quot;).getAbsolutePath();
    +        //        settings.locales_dir_path = new File(basePath + &quot;locales&quot;).getAbsolutePath();
    +<span class="nc" id="L129">        settings.log_file = new File(basePath, &quot;debug.log&quot;).getAbsolutePath();</span>
    +
    +<span class="nc" id="L131">        settings.persist_session_cookies = true;</span>
    +<span class="nc" id="L132">        settings.windowless_rendering_enabled = false;</span>
    +<span class="nc" id="L133">        settings.log_severity = CefSettings.LogSeverity.LOGSEVERITY_INFO;</span>
    +
    +<span class="nc" id="L135">        builder.setAppHandler(</span>
    +<span class="nc" id="L136">                new MavenCefAppHandlerAdapter() {</span>
    +                    @Override
    +                    public void stateHasChanged(org.cef.CefApp.CefAppState state) {
    +<span class="nc" id="L139">                        log.info(&quot;CEF state changed: &quot; + state);</span>
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">                        if (state == CefApp.CefAppState.TERMINATED) {</span>
    +<span class="nc" id="L141">                            System.exit(0);</span>
    +                        }
    +<span class="nc" id="L143">                    }</span>
    +                });
    +<span class="nc" id="L145">    }</span>
    +
    +    private void setupDownloadHandler() {
    +<span class="nc" id="L148">        client.addDownloadHandler(</span>
    +<span class="nc" id="L149">                new CefDownloadHandlerAdapter() {</span>
    +                    @Override
    +                    public boolean onBeforeDownload(
    +                            CefBrowser browser,
    +                            CefDownloadItem downloadItem,
    +                            String suggestedName,
    +                            CefBeforeDownloadCallback callback) {
    +<span class="nc" id="L156">                        callback.Continue(&quot;&quot;, true);</span>
    +<span class="nc" id="L157">                        return true;</span>
    +                    }
    +
    +                    @Override
    +                    public void onDownloadUpdated(
    +                            CefBrowser browser,
    +                            CefDownloadItem downloadItem,
    +                            CefDownloadItemCallback callback) {
    +<span class="nc bnc" id="L165" title="All 2 branches missed.">                        if (downloadItem.isComplete()) {</span>
    +<span class="nc" id="L166">                            log.info(&quot;Download completed: &quot; + downloadItem.getFullPath());</span>
    +<span class="nc bnc" id="L167" title="All 2 branches missed.">                        } else if (downloadItem.isCanceled()) {</span>
    +<span class="nc" id="L168">                            log.info(&quot;Download canceled: &quot; + downloadItem.getFullPath());</span>
    +                        }
    +<span class="nc" id="L170">                    }</span>
    +                });
    +<span class="nc" id="L172">    }</span>
    +
    +    private ConsoleProgressHandler createProgressHandler() {
    +<span class="nc" id="L175">        return new ConsoleProgressHandler() {</span>
    +            @Override
    +            public void handleProgress(EnumProgress state, float percent) {
    +<span class="nc" id="L178">                Objects.requireNonNull(state, &quot;state cannot be null&quot;);</span>
    +<span class="nc" id="L179">                SwingUtilities.invokeLater(</span>
    +                        () -&gt; {
    +<span class="nc bnc" id="L181" title="All 2 branches missed.">                            if (loadingWindow != null) {</span>
    +<span class="nc bnc" id="L182" title="All 6 branches missed.">                                switch (state) {</span>
    +                                    case LOCATING:
    +<span class="nc" id="L184">                                        loadingWindow.setStatus(&quot;Locating Files...&quot;);</span>
    +<span class="nc" id="L185">                                        loadingWindow.setProgress(0);</span>
    +<span class="nc" id="L186">                                        break;</span>
    +                                    case DOWNLOADING:
    +<span class="nc bnc" id="L188" title="All 2 branches missed.">                                        if (percent &gt;= 0) {</span>
    +<span class="nc" id="L189">                                            loadingWindow.setStatus(</span>
    +<span class="nc" id="L190">                                                    String.format(</span>
    +                                                            &quot;Downloading additional files: %.0f%%&quot;,
    +<span class="nc" id="L192">                                                            percent));</span>
    +<span class="nc" id="L193">                                            loadingWindow.setProgress((int) percent);</span>
    +                                        }
    +                                        break;
    +                                    case EXTRACTING:
    +<span class="nc" id="L197">                                        loadingWindow.setStatus(&quot;Extracting files...&quot;);</span>
    +<span class="nc" id="L198">                                        loadingWindow.setProgress(60);</span>
    +<span class="nc" id="L199">                                        break;</span>
    +                                    case INITIALIZING:
    +<span class="nc" id="L201">                                        loadingWindow.setStatus(&quot;Initializing UI...&quot;);</span>
    +<span class="nc" id="L202">                                        loadingWindow.setProgress(80);</span>
    +<span class="nc" id="L203">                                        break;</span>
    +                                    case INITIALIZED:
    +<span class="nc" id="L205">                                        loadingWindow.setStatus(&quot;Finalising startup...&quot;);</span>
    +<span class="nc" id="L206">                                        loadingWindow.setProgress(90);</span>
    +                                        break;
    +                                }
    +                            }
    +<span class="nc" id="L210">                        });</span>
    +<span class="nc" id="L211">            }</span>
    +        };
    +    }
    +
    +    private void setupMainFrame() {
    +<span class="nc" id="L216">        frame = new JFrame(&quot;Stirling-PDF&quot;);</span>
    +<span class="nc" id="L217">        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);</span>
    +<span class="nc" id="L218">        frame.setUndecorated(true);</span>
    +<span class="nc" id="L219">        frame.setOpacity(0.0f);</span>
    +
    +<span class="nc" id="L221">        JPanel contentPane = new JPanel(new BorderLayout());</span>
    +<span class="nc" id="L222">        contentPane.setDoubleBuffered(true);</span>
    +<span class="nc" id="L223">        contentPane.add(browser.getUIComponent(), BorderLayout.CENTER);</span>
    +<span class="nc" id="L224">        frame.setContentPane(contentPane);</span>
    +
    +<span class="nc" id="L226">        frame.addWindowListener(</span>
    +<span class="nc" id="L227">                new java.awt.event.WindowAdapter() {</span>
    +                    @Override
    +                    public void windowClosing(java.awt.event.WindowEvent windowEvent) {
    +<span class="nc" id="L230">                        cleanup();</span>
    +<span class="nc" id="L231">                        System.exit(0);</span>
    +<span class="nc" id="L232">                    }</span>
    +                });
    +
    +<span class="nc" id="L235">        frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800));</span>
    +<span class="nc" id="L236">        frame.setLocationRelativeTo(null);</span>
    +
    +<span class="nc" id="L238">        loadIcon();</span>
    +<span class="nc" id="L239">    }</span>
    +
    +    private void setupLoadHandler() {
    +<span class="nc" id="L242">        final long initStartTime = System.currentTimeMillis();</span>
    +<span class="nc" id="L243">        log.info(&quot;Setting up load handler at: {}&quot;, initStartTime);</span>
    +
    +<span class="nc" id="L245">        client.addLoadHandler(</span>
    +<span class="nc" id="L246">                new CefLoadHandlerAdapter() {</span>
    +                    @Override
    +                    public void onLoadingStateChange(
    +                            CefBrowser browser,
    +                            boolean isLoading,
    +                            boolean canGoBack,
    +                            boolean canGoForward) {
    +<span class="nc" id="L253">                        log.debug(</span>
    +                                &quot;Loading state change - isLoading: {}, canGoBack: {}, canGoForward:&quot;
    +                                        + &quot; {}, browserInitialized: {}, Time elapsed: {}ms&quot;,
    +<span class="nc" id="L256">                                isLoading,</span>
    +<span class="nc" id="L257">                                canGoBack,</span>
    +<span class="nc" id="L258">                                canGoForward,</span>
    +<span class="nc" id="L259">                                browserInitialized,</span>
    +<span class="nc" id="L260">                                System.currentTimeMillis() - initStartTime);</span>
    +
    +<span class="nc bnc" id="L262" title="All 4 branches missed.">                        if (!isLoading &amp;&amp; !browserInitialized) {</span>
    +<span class="nc" id="L263">                            log.info(</span>
    +                                    &quot;Browser finished loading, preparing to initialize UI&quot;
    +                                            + &quot; components&quot;);
    +<span class="nc" id="L266">                            browserInitialized = true;</span>
    +<span class="nc" id="L267">                            SwingUtilities.invokeLater(</span>
    +                                    () -&gt; {
    +                                        try {
    +<span class="nc bnc" id="L270" title="All 2 branches missed.">                                            if (loadingWindow != null) {</span>
    +<span class="nc" id="L271">                                                log.info(&quot;Starting UI initialization sequence&quot;);</span>
    +
    +                                                // Close loading window first
    +<span class="nc" id="L274">                                                loadingWindow.setVisible(false);</span>
    +<span class="nc" id="L275">                                                loadingWindow.dispose();</span>
    +<span class="nc" id="L276">                                                loadingWindow = null;</span>
    +<span class="nc" id="L277">                                                log.info(&quot;Loading window disposed&quot;);</span>
    +
    +                                                // Then setup the main frame
    +<span class="nc" id="L280">                                                frame.setVisible(false);</span>
    +<span class="nc" id="L281">                                                frame.dispose();</span>
    +<span class="nc" id="L282">                                                frame.setOpacity(1.0f);</span>
    +<span class="nc" id="L283">                                                frame.setUndecorated(false);</span>
    +<span class="nc" id="L284">                                                frame.pack();</span>
    +<span class="nc" id="L285">                                                frame.setSize(</span>
    +<span class="nc" id="L286">                                                        UIScaling.scaleWidth(1280),</span>
    +<span class="nc" id="L287">                                                        UIScaling.scaleHeight(800));</span>
    +<span class="nc" id="L288">                                                frame.setLocationRelativeTo(null);</span>
    +<span class="nc" id="L289">                                                log.debug(&quot;Frame reconfigured&quot;);</span>
    +
    +                                                // Show the main frame
    +<span class="nc" id="L292">                                                frame.setVisible(true);</span>
    +<span class="nc" id="L293">                                                frame.requestFocus();</span>
    +<span class="nc" id="L294">                                                frame.toFront();</span>
    +<span class="nc" id="L295">                                                log.info(&quot;Main frame displayed and focused&quot;);</span>
    +
    +                                                // Focus the browser component
    +<span class="nc" id="L298">                                                Timer focusTimer =</span>
    +                                                        new Timer(
    +                                                                100,
    +                                                                e -&gt; {
    +                                                                    try {
    +<span class="nc" id="L303">                                                                        browser.getUIComponent()</span>
    +<span class="nc" id="L304">                                                                                .requestFocus();</span>
    +<span class="nc" id="L305">                                                                        log.info(</span>
    +                                                                                &quot;Browser component&quot;
    +                                                                                        + &quot; focused&quot;);
    +<span class="nc" id="L308">                                                                    } catch (Exception ex) {</span>
    +<span class="nc" id="L309">                                                                        log.error(</span>
    +                                                                                &quot;Error focusing&quot;
    +                                                                                        + &quot; browser&quot;,
    +                                                                                ex);
    +<span class="nc" id="L313">                                                                    }</span>
    +<span class="nc" id="L314">                                                                });</span>
    +<span class="nc" id="L315">                                                focusTimer.setRepeats(false);</span>
    +<span class="nc" id="L316">                                                focusTimer.start();</span>
    +                                            }
    +<span class="nc" id="L318">                                        } catch (Exception e) {</span>
    +<span class="nc" id="L319">                                            log.error(&quot;Error during UI initialization&quot;, e);</span>
    +                                            // Attempt cleanup on error
    +<span class="nc bnc" id="L321" title="All 2 branches missed.">                                            if (loadingWindow != null) {</span>
    +<span class="nc" id="L322">                                                loadingWindow.dispose();</span>
    +<span class="nc" id="L323">                                                loadingWindow = null;</span>
    +                                            }
    +<span class="nc bnc" id="L325" title="All 2 branches missed.">                                            if (frame != null) {</span>
    +<span class="nc" id="L326">                                                frame.setVisible(true);</span>
    +<span class="nc" id="L327">                                                frame.requestFocus();</span>
    +                                            }
    +<span class="nc" id="L329">                                        }</span>
    +<span class="nc" id="L330">                                    });</span>
    +                        }
    +<span class="nc" id="L332">                    }</span>
    +                });
    +<span class="nc" id="L334">    }</span>
    +
    +    private void setupTrayIcon(Image icon) {
    +<span class="nc bnc" id="L337" title="All 2 branches missed.">        if (!SystemTray.isSupported()) {</span>
    +<span class="nc" id="L338">            log.warn(&quot;System tray is not supported&quot;);</span>
    +<span class="nc" id="L339">            return;</span>
    +        }
    +
    +        try {
    +<span class="nc" id="L343">            systemTray = SystemTray.getSystemTray();</span>
    +
    +            // Create popup menu
    +<span class="nc" id="L346">            PopupMenu popup = new PopupMenu();</span>
    +
    +            // Create menu items
    +<span class="nc" id="L349">            MenuItem showItem = new MenuItem(&quot;Show&quot;);</span>
    +<span class="nc" id="L350">            showItem.addActionListener(</span>
    +                    e -&gt; {
    +<span class="nc" id="L352">                        frame.setVisible(true);</span>
    +<span class="nc" id="L353">                        frame.setState(Frame.NORMAL);</span>
    +<span class="nc" id="L354">                    });</span>
    +
    +<span class="nc" id="L356">            MenuItem exitItem = new MenuItem(&quot;Exit&quot;);</span>
    +<span class="nc" id="L357">            exitItem.addActionListener(</span>
    +                    e -&gt; {
    +<span class="nc" id="L359">                        cleanup();</span>
    +<span class="nc" id="L360">                        System.exit(0);</span>
    +<span class="nc" id="L361">                    });</span>
    +
    +            // Add menu items to popup menu
    +<span class="nc" id="L364">            popup.add(showItem);</span>
    +<span class="nc" id="L365">            popup.addSeparator();</span>
    +<span class="nc" id="L366">            popup.add(exitItem);</span>
    +
    +            // Create tray icon
    +<span class="nc" id="L369">            trayIcon = new TrayIcon(icon, &quot;Stirling-PDF&quot;, popup);</span>
    +<span class="nc" id="L370">            trayIcon.setImageAutoSize(true);</span>
    +
    +            // Add double-click behavior
    +<span class="nc" id="L373">            trayIcon.addActionListener(</span>
    +                    e -&gt; {
    +<span class="nc" id="L375">                        frame.setVisible(true);</span>
    +<span class="nc" id="L376">                        frame.setState(Frame.NORMAL);</span>
    +<span class="nc" id="L377">                    });</span>
    +
    +            // Add tray icon to system tray
    +<span class="nc" id="L380">            systemTray.add(trayIcon);</span>
    +
    +            // Modify frame behavior to minimize to tray
    +<span class="nc" id="L383">            frame.addWindowStateListener(</span>
    +<span class="nc" id="L384">                    new WindowStateListener() {</span>
    +                        public void windowStateChanged(WindowEvent e) {
    +<span class="nc bnc" id="L386" title="All 2 branches missed.">                            if (e.getNewState() == Frame.ICONIFIED) {</span>
    +<span class="nc" id="L387">                                frame.setVisible(false);</span>
    +                            }
    +<span class="nc" id="L389">                        }</span>
    +                    });
    +
    +<span class="nc" id="L392">        } catch (AWTException e) {</span>
    +<span class="nc" id="L393">            log.error(&quot;Error setting up system tray icon&quot;, e);</span>
    +<span class="nc" id="L394">        }</span>
    +<span class="nc" id="L395">    }</span>
    +
    +    private void loadIcon() {
    +        try {
    +<span class="nc" id="L399">            Image icon = null;</span>
    +<span class="nc" id="L400">            String[] iconPaths = {&quot;/static/favicon.ico&quot;};</span>
    +
    +<span class="nc bnc" id="L402" title="All 2 branches missed.">            for (String path : iconPaths) {</span>
    +<span class="nc bnc" id="L403" title="All 2 branches missed.">                if (icon != null) break;</span>
    +                try {
    +<span class="nc" id="L405">                    try (InputStream is = getClass().getResourceAsStream(path)) {</span>
    +<span class="nc bnc" id="L406" title="All 2 branches missed.">                        if (is != null) {</span>
    +<span class="nc" id="L407">                            icon = ImageIO.read(is);</span>
    +<span class="nc" id="L408">                            break;</span>
    +                        }
    +<span class="nc bnc" id="L410" title="All 2 branches missed.">                    }</span>
    +<span class="nc" id="L411">                } catch (Exception e) {</span>
    +<span class="nc" id="L412">                    log.debug(&quot;Could not load icon from &quot; + path, e);</span>
    +<span class="nc" id="L413">                }</span>
    +            }
    +
    +<span class="nc bnc" id="L416" title="All 2 branches missed.">            if (icon != null) {</span>
    +<span class="nc" id="L417">                frame.setIconImage(icon);</span>
    +<span class="nc" id="L418">                setupTrayIcon(icon);</span>
    +            } else {
    +<span class="nc" id="L420">                log.warn(&quot;Could not load icon from any source&quot;);</span>
    +            }
    +<span class="nc" id="L422">        } catch (Exception e) {</span>
    +<span class="nc" id="L423">            log.error(&quot;Error loading icon&quot;, e);</span>
    +<span class="nc" id="L424">        }</span>
    +<span class="nc" id="L425">    }</span>
    +
    +    @PreDestroy
    +    public void cleanup() {
    +<span class="nc bnc" id="L429" title="All 2 branches missed.">        if (browser != null) browser.close(true);</span>
    +<span class="nc bnc" id="L430" title="All 2 branches missed.">        if (client != null) client.dispose();</span>
    +<span class="nc bnc" id="L431" title="All 2 branches missed.">        if (cefApp != null) cefApp.dispose();</span>
    +<span class="nc bnc" id="L432" title="All 2 branches missed.">        if (loadingWindow != null) loadingWindow.dispose();</span>
    +<span class="nc" id="L433">    }</span>
    +
    +    public static void forceInitializeUI() {
    +        try {
    +<span class="nc bnc" id="L437" title="All 2 branches missed.">            if (loadingWindow != null) {</span>
    +<span class="nc" id="L438">                log.info(&quot;Forcing start of UI initialization sequence&quot;);</span>
    +
    +                // Close loading window first
    +<span class="nc" id="L441">                loadingWindow.setVisible(false);</span>
    +<span class="nc" id="L442">                loadingWindow.dispose();</span>
    +<span class="nc" id="L443">                loadingWindow = null;</span>
    +<span class="nc" id="L444">                log.info(&quot;Loading window disposed&quot;);</span>
    +
    +                // Then setup the main frame
    +<span class="nc" id="L447">                frame.setVisible(false);</span>
    +<span class="nc" id="L448">                frame.dispose();</span>
    +<span class="nc" id="L449">                frame.setOpacity(1.0f);</span>
    +<span class="nc" id="L450">                frame.setUndecorated(false);</span>
    +<span class="nc" id="L451">                frame.pack();</span>
    +<span class="nc" id="L452">                frame.setSize(UIScaling.scaleWidth(1280), UIScaling.scaleHeight(800));</span>
    +<span class="nc" id="L453">                frame.setLocationRelativeTo(null);</span>
    +<span class="nc" id="L454">                log.debug(&quot;Frame reconfigured&quot;);</span>
    +
    +                // Show the main frame
    +<span class="nc" id="L457">                frame.setVisible(true);</span>
    +<span class="nc" id="L458">                frame.requestFocus();</span>
    +<span class="nc" id="L459">                frame.toFront();</span>
    +<span class="nc" id="L460">                log.info(&quot;Main frame displayed and focused&quot;);</span>
    +
    +                // Focus the browser component if available
    +<span class="nc bnc" id="L463" title="All 2 branches missed.">                if (browser != null) {</span>
    +<span class="nc" id="L464">                    Timer focusTimer =</span>
    +                            new Timer(
    +                                    100,
    +                                    e -&gt; {
    +                                        try {
    +<span class="nc" id="L469">                                            browser.getUIComponent().requestFocus();</span>
    +<span class="nc" id="L470">                                            log.info(&quot;Browser component focused&quot;);</span>
    +<span class="nc" id="L471">                                        } catch (Exception ex) {</span>
    +<span class="nc" id="L472">                                            log.error(</span>
    +                                                    &quot;Error focusing browser during force ui&quot;
    +                                                            + &quot; initialization.&quot;,
    +                                                    ex);
    +<span class="nc" id="L476">                                        }</span>
    +<span class="nc" id="L477">                                    });</span>
    +<span class="nc" id="L478">                    focusTimer.setRepeats(false);</span>
    +<span class="nc" id="L479">                    focusTimer.start();</span>
    +                }
    +            }
    +<span class="nc" id="L482">        } catch (Exception e) {</span>
    +<span class="nc" id="L483">            log.error(&quot;Error during Forced UI initialization.&quot;, e);</span>
    +            // Attempt cleanup on error
    +<span class="nc bnc" id="L485" title="All 2 branches missed.">            if (loadingWindow != null) {</span>
    +<span class="nc" id="L486">                loadingWindow.dispose();</span>
    +<span class="nc" id="L487">                loadingWindow = null;</span>
    +            }
    +<span class="nc bnc" id="L489" title="All 2 branches missed.">            if (frame != null) {</span>
    +<span class="nc" id="L490">                frame.setVisible(true);</span>
    +<span class="nc" id="L491">                frame.setOpacity(1.0f);</span>
    +<span class="nc" id="L492">                frame.setUndecorated(false);</span>
    +<span class="nc" id="L493">                frame.requestFocus();</span>
    +            }
    +<span class="nc" id="L495">        }</span>
    +<span class="nc" id="L496">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/LoadingWindow.html b/testResult/html/stirling.software.SPDF.UI.impl/LoadingWindow.html
    new file mode 100644
    index 000000000..4f34160f7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/LoadingWindow.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LoadingWindow</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_class">LoadingWindow</span></div><h1>LoadingWindow</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">786 of 786</td><td class="ctr2">0%</td><td class="bar">38 of 38</td><td class="ctr2">0%</td><td class="ctr1">29</td><td class="ctr2">29</td><td class="ctr1">173</td><td class="ctr2">173</td><td class="ctr1">10</td><td class="ctr2">10</td></tr></tfoot><tbody><tr><td id="a6"><a href="LoadingWindow.java.html#L29" class="el_method">LoadingWindow(Frame, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="239" alt="239"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h0">56</td><td class="ctr2" id="i0">56</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="LoadingWindow.java.html#L124" class="el_method">lambda$checkAndRefreshExplorer$1(ActionEvent)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="86" height="10" title="173" alt="173"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h2">29</td><td class="ctr2" id="i2">29</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="LoadingWindow.java.html#L184" class="el_method">lambda$checkAndRefreshExplorer$0(Set, ActionEvent)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="140" alt="140"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h1">30</td><td class="ctr2" id="i1">30</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="LoadingWindow.java.html#L275" class="el_method">lambda$setProgress$2(int)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="121" alt="121"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h3">28</td><td class="ctr2" id="i3">28</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="LoadingWindow.java.html#L330" class="el_method">lambda$setStatus$3(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="46" alt="46"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h4">10</td><td class="ctr2" id="i4">10</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="LoadingWindow.java.html#L115" class="el_method">checkAndRefreshExplorer()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="27" alt="27"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h5">8</td><td class="ctr2" id="i5">8</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a8"><a href="LoadingWindow.java.html#L322" class="el_method">setStatus(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="14" alt="14"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">4</td><td class="ctr2" id="i6">4</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a1"><a href="LoadingWindow.java.html#L348" class="el_method">dispose()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="11" alt="11"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a9"><a href="LoadingWindow.java.html#L20" class="el_method">static {...}</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="10" alt="10"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">3</td><td class="ctr2" id="i8">3</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a7"><a href="LoadingWindow.java.html#L272" class="el_method">setProgress(int)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="5" alt="5"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/LoadingWindow.java.html b/testResult/html/stirling.software.SPDF.UI.impl/LoadingWindow.java.html
    new file mode 100644
    index 000000000..854d5062c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/LoadingWindow.java.html
    @@ -0,0 +1,352 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LoadingWindow.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.UI.impl</a> &gt; <span class="el_source">LoadingWindow.java</span></div><h1>LoadingWindow.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.UI.impl;
    +
    +import java.awt.*;
    +import java.io.BufferedReader;
    +import java.io.InputStream;
    +import java.io.InputStreamReader;
    +import java.util.HashSet;
    +import java.util.Set;
    +import java.util.concurrent.TimeUnit;
    +
    +import javax.imageio.ImageIO;
    +import javax.swing.*;
    +
    +import io.github.pixee.security.BoundedLineReader;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.utils.UIScaling;
    +
    +<span class="nc" id="L20">@Slf4j</span>
    +public class LoadingWindow extends JDialog {
    +    private final JProgressBar progressBar;
    +    private final JLabel statusLabel;
    +    private final JPanel mainPanel;
    +    private final JLabel brandLabel;
    +    private long startTime;
    +
    +    private Timer stuckTimer;
    +<span class="nc" id="L29">    private long stuckThreshold = 4000;</span>
    +<span class="nc" id="L30">    private long timeAt90Percent = -1;</span>
    +    private volatile Process explorerProcess;
    +<span class="nc" id="L32">    private static final boolean IS_WINDOWS =</span>
    +<span class="nc" id="L33">            System.getProperty(&quot;os.name&quot;).toLowerCase().contains(&quot;win&quot;);</span>
    +
    +    public LoadingWindow(Frame parent, String initialUrl) {
    +<span class="nc" id="L36">        super(parent, &quot;Initializing Stirling-PDF&quot;, true);</span>
    +<span class="nc" id="L37">        startTime = System.currentTimeMillis();</span>
    +<span class="nc" id="L38">        log.info(&quot;Creating LoadingWindow - initialization started at: {}&quot;, startTime);</span>
    +
    +        // Initialize components
    +<span class="nc" id="L41">        mainPanel = new JPanel();</span>
    +<span class="nc" id="L42">        mainPanel.setBackground(Color.WHITE);</span>
    +<span class="nc" id="L43">        mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 20, 30));</span>
    +<span class="nc" id="L44">        mainPanel.setLayout(new GridBagLayout());</span>
    +<span class="nc" id="L45">        GridBagConstraints gbc = new GridBagConstraints();</span>
    +
    +        // Configure GridBagConstraints
    +<span class="nc" id="L48">        gbc.gridwidth = GridBagConstraints.REMAINDER;</span>
    +<span class="nc" id="L49">        gbc.fill = GridBagConstraints.HORIZONTAL;</span>
    +<span class="nc" id="L50">        gbc.insets = new Insets(5, 5, 5, 5);</span>
    +<span class="nc" id="L51">        gbc.weightx = 1.0;</span>
    +<span class="nc" id="L52">        gbc.weighty = 0.0;</span>
    +
    +        // Add icon
    +        try {
    +<span class="nc" id="L56">            try (InputStream is = getClass().getResourceAsStream(&quot;/static/favicon.ico&quot;)) {</span>
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">                if (is != null) {</span>
    +<span class="nc" id="L58">                    Image img = ImageIO.read(is);</span>
    +<span class="nc bnc" id="L59" title="All 2 branches missed.">                    if (img != null) {</span>
    +<span class="nc" id="L60">                        Image scaledImg = UIScaling.scaleIcon(img, 48, 48);</span>
    +<span class="nc" id="L61">                        JLabel iconLabel = new JLabel(new ImageIcon(scaledImg));</span>
    +<span class="nc" id="L62">                        iconLabel.setHorizontalAlignment(SwingConstants.CENTER);</span>
    +<span class="nc" id="L63">                        gbc.gridy = 0;</span>
    +<span class="nc" id="L64">                        mainPanel.add(iconLabel, gbc);</span>
    +<span class="nc" id="L65">                        log.info(&quot;Icon loaded and scaled successfully&quot;);</span>
    +                    }
    +                }
    +            }
    +<span class="nc" id="L69">        } catch (Exception e) {</span>
    +<span class="nc" id="L70">            log.error(&quot;Failed to load icon&quot;, e);</span>
    +<span class="nc" id="L71">        }</span>
    +
    +        // URL Label with explicit size
    +<span class="nc" id="L74">        brandLabel = new JLabel(initialUrl);</span>
    +<span class="nc" id="L75">        brandLabel.setHorizontalAlignment(SwingConstants.CENTER);</span>
    +<span class="nc" id="L76">        brandLabel.setPreferredSize(new Dimension(300, 25));</span>
    +<span class="nc" id="L77">        brandLabel.setText(&quot;Stirling-PDF&quot;);</span>
    +<span class="nc" id="L78">        gbc.gridy = 1;</span>
    +<span class="nc" id="L79">        mainPanel.add(brandLabel, gbc);</span>
    +
    +        // Status label with explicit size
    +<span class="nc" id="L82">        statusLabel = new JLabel(&quot;Initializing...&quot;);</span>
    +<span class="nc" id="L83">        statusLabel.setHorizontalAlignment(SwingConstants.CENTER);</span>
    +<span class="nc" id="L84">        statusLabel.setPreferredSize(new Dimension(300, 25));</span>
    +<span class="nc" id="L85">        gbc.gridy = 2;</span>
    +<span class="nc" id="L86">        mainPanel.add(statusLabel, gbc);</span>
    +
    +        // Progress bar with explicit size
    +<span class="nc" id="L89">        progressBar = new JProgressBar(0, 100);</span>
    +<span class="nc" id="L90">        progressBar.setStringPainted(true);</span>
    +<span class="nc" id="L91">        progressBar.setPreferredSize(new Dimension(300, 25));</span>
    +<span class="nc" id="L92">        gbc.gridy = 3;</span>
    +<span class="nc" id="L93">        mainPanel.add(progressBar, gbc);</span>
    +
    +        // Set dialog properties
    +<span class="nc" id="L96">        setContentPane(mainPanel);</span>
    +<span class="nc" id="L97">        setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);</span>
    +<span class="nc" id="L98">        setResizable(false);</span>
    +<span class="nc" id="L99">        setUndecorated(false);</span>
    +
    +        // Set size and position
    +<span class="nc" id="L102">        setSize(UIScaling.scaleWidth(400), UIScaling.scaleHeight(200));</span>
    +
    +<span class="nc" id="L104">        setLocationRelativeTo(parent);</span>
    +<span class="nc" id="L105">        setAlwaysOnTop(true);</span>
    +<span class="nc" id="L106">        setProgress(0);</span>
    +<span class="nc" id="L107">        setStatus(&quot;Starting...&quot;);</span>
    +
    +<span class="nc" id="L109">        log.info(</span>
    +                &quot;LoadingWindow initialization completed in {}ms&quot;,
    +<span class="nc" id="L111">                System.currentTimeMillis() - startTime);</span>
    +<span class="nc" id="L112">    }</span>
    +
    +    private void checkAndRefreshExplorer() {
    +<span class="nc bnc" id="L115" title="All 2 branches missed.">        if (!IS_WINDOWS) {</span>
    +<span class="nc" id="L116">            return;</span>
    +        }
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">        if (timeAt90Percent == -1) {</span>
    +<span class="nc" id="L119">            timeAt90Percent = System.currentTimeMillis();</span>
    +<span class="nc" id="L120">            stuckTimer =</span>
    +                    new Timer(
    +                            1000,
    +                            e -&gt; {
    +<span class="nc" id="L124">                                long currentTime = System.currentTimeMillis();</span>
    +<span class="nc bnc" id="L125" title="All 2 branches missed.">                                if (currentTime - timeAt90Percent &gt; stuckThreshold) {</span>
    +                                    try {
    +<span class="nc" id="L127">                                        log.debug(</span>
    +                                                &quot;Attempting Windows explorer refresh due to 90% stuck state&quot;);
    +<span class="nc" id="L129">                                        String currentDir = System.getProperty(&quot;user.dir&quot;);</span>
    +
    +                                        // Store current explorer PIDs before we start new one
    +<span class="nc" id="L132">                                        Set&lt;String&gt; existingPids = new HashSet&lt;&gt;();</span>
    +<span class="nc" id="L133">                                        ProcessBuilder listExplorer =</span>
    +                                                new ProcessBuilder(
    +                                                        &quot;cmd&quot;,
    +                                                        &quot;/c&quot;,
    +                                                        &quot;wmic&quot;,
    +                                                        &quot;process&quot;,
    +                                                        &quot;where&quot;,
    +                                                        &quot;name='explorer.exe'&quot;,
    +                                                        &quot;get&quot;,
    +                                                        &quot;ProcessId&quot;,
    +                                                        &quot;/format:csv&quot;);
    +<span class="nc" id="L144">                                        Process process = listExplorer.start();</span>
    +<span class="nc" id="L145">                                        BufferedReader reader =</span>
    +                                                new BufferedReader(
    +                                                        new InputStreamReader(
    +<span class="nc" id="L148">                                                                process.getInputStream()));</span>
    +                                        String line;
    +<span class="nc" id="L150">                                        while ((line =</span>
    +<span class="nc bnc" id="L151" title="All 2 branches missed.">                                                        BoundedLineReader.readLine(</span>
    +                                                                reader, 5_000_000))
    +                                                != null) {
    +<span class="nc bnc" id="L154" title="All 2 branches missed.">                                            if (line.matches(&quot;.*\\d+.*&quot;)) { // Contains numbers</span>
    +<span class="nc" id="L155">                                                String[] parts = line.trim().split(&quot;,&quot;);</span>
    +<span class="nc bnc" id="L156" title="All 2 branches missed.">                                                if (parts.length &gt;= 2) {</span>
    +<span class="nc" id="L157">                                                    existingPids.add(</span>
    +<span class="nc" id="L158">                                                            parts[parts.length - 1].trim());</span>
    +                                                }
    +<span class="nc" id="L160">                                            }</span>
    +                                        }
    +<span class="nc" id="L162">                                        process.waitFor(2, TimeUnit.SECONDS);</span>
    +
    +                                        // Start new explorer
    +<span class="nc" id="L165">                                        ProcessBuilder pb =</span>
    +                                                new ProcessBuilder(
    +                                                        &quot;cmd&quot;,
    +                                                        &quot;/c&quot;,
    +                                                        &quot;start&quot;,
    +                                                        &quot;/min&quot;,
    +                                                        &quot;/b&quot;,
    +                                                        &quot;explorer.exe&quot;,
    +                                                        currentDir);
    +<span class="nc" id="L174">                                        pb.redirectErrorStream(true);</span>
    +<span class="nc" id="L175">                                        explorerProcess = pb.start();</span>
    +
    +                                        // Schedule cleanup
    +<span class="nc" id="L178">                                        Timer cleanupTimer =</span>
    +                                                new Timer(
    +                                                        2000,
    +                                                        cleanup -&gt; {
    +                                                            try {
    +                                                                // Find new explorer processes
    +<span class="nc" id="L184">                                                                ProcessBuilder findNewExplorer =</span>
    +                                                                        new ProcessBuilder(
    +                                                                                &quot;cmd&quot;,
    +                                                                                &quot;/c&quot;,
    +                                                                                &quot;wmic&quot;,
    +                                                                                &quot;process&quot;,
    +                                                                                &quot;where&quot;,
    +                                                                                &quot;name='explorer.exe'&quot;,
    +                                                                                &quot;get&quot;,
    +                                                                                &quot;ProcessId&quot;,
    +                                                                                &quot;/format:csv&quot;);
    +<span class="nc" id="L195">                                                                Process newProcess =</span>
    +<span class="nc" id="L196">                                                                        findNewExplorer.start();</span>
    +<span class="nc" id="L197">                                                                BufferedReader newReader =</span>
    +                                                                        new BufferedReader(
    +                                                                                new InputStreamReader(
    +                                                                                        newProcess
    +<span class="nc" id="L201">                                                                                                .getInputStream()));</span>
    +                                                                String newLine;
    +<span class="nc" id="L203">                                                                while ((newLine =</span>
    +                                                                                BoundedLineReader
    +<span class="nc bnc" id="L205" title="All 2 branches missed.">                                                                                        .readLine(</span>
    +                                                                                                newReader,
    +                                                                                                5_000_000))
    +                                                                        != null) {
    +<span class="nc bnc" id="L209" title="All 2 branches missed.">                                                                    if (newLine.matches(</span>
    +                                                                            &quot;.*\\d+.*&quot;)) {
    +<span class="nc" id="L211">                                                                        String[] parts =</span>
    +<span class="nc" id="L212">                                                                                newLine.trim()</span>
    +<span class="nc" id="L213">                                                                                        .split(&quot;,&quot;);</span>
    +<span class="nc bnc" id="L214" title="All 2 branches missed.">                                                                        if (parts.length &gt;= 2) {</span>
    +<span class="nc" id="L215">                                                                            String pid =</span>
    +                                                                                    parts[
    +                                                                                            parts.length
    +                                                                                                    - 1]
    +<span class="nc" id="L219">                                                                                            .trim();</span>
    +<span class="nc" id="L220">                                                                            if (!existingPids</span>
    +<span class="nc bnc" id="L221" title="All 2 branches missed.">                                                                                    .contains(</span>
    +                                                                                            pid)) {
    +<span class="nc" id="L223">                                                                                log.debug(</span>
    +                                                                                        &quot;Found new explorer.exe with PID: &quot;
    +                                                                                                + pid);
    +                                                                                ProcessBuilder
    +<span class="nc" id="L227">                                                                                        killProcess =</span>
    +                                                                                                new ProcessBuilder(
    +                                                                                                        &quot;taskkill&quot;,
    +                                                                                                        &quot;/PID&quot;,
    +                                                                                                        pid,
    +                                                                                                        &quot;/F&quot;);
    +<span class="nc" id="L233">                                                                                killProcess</span>
    +<span class="nc" id="L234">                                                                                        .redirectErrorStream(</span>
    +                                                                                                true);
    +<span class="nc" id="L236">                                                                                Process killResult =</span>
    +                                                                                        killProcess
    +<span class="nc" id="L238">                                                                                                .start();</span>
    +<span class="nc" id="L239">                                                                                killResult.waitFor(</span>
    +                                                                                        2,
    +                                                                                        TimeUnit
    +                                                                                                .SECONDS);
    +<span class="nc" id="L243">                                                                                log.debug(</span>
    +                                                                                        &quot;Explorer process terminated: &quot;
    +                                                                                                + pid);
    +                                                                            }
    +                                                                        }
    +<span class="nc" id="L248">                                                                    }</span>
    +                                                                }
    +<span class="nc" id="L250">                                                                newProcess.waitFor(</span>
    +                                                                        2, TimeUnit.SECONDS);
    +<span class="nc" id="L252">                                                            } catch (Exception ex) {</span>
    +<span class="nc" id="L253">                                                                log.error(</span>
    +                                                                        &quot;Error cleaning up Windows explorer process&quot;,
    +                                                                        ex);
    +<span class="nc" id="L256">                                                            }</span>
    +<span class="nc" id="L257">                                                        });</span>
    +<span class="nc" id="L258">                                        cleanupTimer.setRepeats(false);</span>
    +<span class="nc" id="L259">                                        cleanupTimer.start();</span>
    +<span class="nc" id="L260">                                        stuckTimer.stop();</span>
    +<span class="nc" id="L261">                                    } catch (Exception ex) {</span>
    +<span class="nc" id="L262">                                        log.error(&quot;Error refreshing Windows explorer&quot;, ex);</span>
    +<span class="nc" id="L263">                                    }</span>
    +                                }
    +<span class="nc" id="L265">                            });</span>
    +<span class="nc" id="L266">            stuckTimer.setRepeats(true);</span>
    +<span class="nc" id="L267">            stuckTimer.start();</span>
    +        }
    +<span class="nc" id="L269">    }</span>
    +
    +    public void setProgress(final int progress) {
    +<span class="nc" id="L272">        SwingUtilities.invokeLater(</span>
    +                () -&gt; {
    +                    try {
    +<span class="nc" id="L275">                        int validProgress = Math.min(Math.max(progress, 0), 100);</span>
    +<span class="nc" id="L276">                        log.info(</span>
    +                                &quot;Setting progress to {}% at {}ms since start&quot;,
    +<span class="nc" id="L278">                                validProgress, System.currentTimeMillis() - startTime);</span>
    +
    +                        // Log additional details when near 90%
    +<span class="nc bnc" id="L281" title="All 4 branches missed.">                        if (validProgress &gt;= 85 &amp;&amp; validProgress &lt;= 95) {</span>
    +<span class="nc" id="L282">                            log.info(</span>
    +                                    &quot;Near 90% progress - Current status: {}, Window visible: {}, &quot;
    +                                            + &quot;Progress bar responding: {}, Memory usage: {}MB&quot;,
    +<span class="nc" id="L285">                                    statusLabel.getText(),</span>
    +<span class="nc" id="L286">                                    isVisible(),</span>
    +<span class="nc" id="L287">                                    progressBar.isEnabled(),</span>
    +<span class="nc" id="L288">                                    Runtime.getRuntime().totalMemory() / (1024 * 1024));</span>
    +
    +                            // Add thread state logging
    +<span class="nc" id="L291">                            Thread currentThread = Thread.currentThread();</span>
    +<span class="nc" id="L292">                            log.info(</span>
    +                                    &quot;Current thread state - Name: {}, State: {}, Priority: {}&quot;,
    +<span class="nc" id="L294">                                    currentThread.getName(),</span>
    +<span class="nc" id="L295">                                    currentThread.getState(),</span>
    +<span class="nc" id="L296">                                    currentThread.getPriority());</span>
    +
    +<span class="nc bnc" id="L298" title="All 4 branches missed.">                            if (validProgress &gt;= 90 &amp;&amp; validProgress &lt; 95) {</span>
    +<span class="nc" id="L299">                                checkAndRefreshExplorer();</span>
    +                            } else {
    +                                // Reset the timer if we move past 95%
    +<span class="nc bnc" id="L302" title="All 2 branches missed.">                                if (validProgress &gt;= 95) {</span>
    +<span class="nc bnc" id="L303" title="All 2 branches missed.">                                    if (stuckTimer != null) {</span>
    +<span class="nc" id="L304">                                        stuckTimer.stop();</span>
    +                                    }
    +<span class="nc" id="L306">                                    timeAt90Percent = -1;</span>
    +                                }
    +                            }
    +                        }
    +
    +<span class="nc" id="L311">                        progressBar.setValue(validProgress);</span>
    +<span class="nc" id="L312">                        progressBar.setString(validProgress + &quot;%&quot;);</span>
    +<span class="nc" id="L313">                        mainPanel.revalidate();</span>
    +<span class="nc" id="L314">                        mainPanel.repaint();</span>
    +<span class="nc" id="L315">                    } catch (Exception e) {</span>
    +<span class="nc" id="L316">                        log.error(&quot;Error updating progress to &quot; + progress, e);</span>
    +<span class="nc" id="L317">                    }</span>
    +<span class="nc" id="L318">                });</span>
    +<span class="nc" id="L319">    }</span>
    +
    +    public void setStatus(final String status) {
    +<span class="nc" id="L322">        log.info(</span>
    +                &quot;Status update at {}ms - Setting status to: {}&quot;,
    +<span class="nc" id="L324">                System.currentTimeMillis() - startTime,</span>
    +                status);
    +
    +<span class="nc" id="L327">        SwingUtilities.invokeLater(</span>
    +                () -&gt; {
    +                    try {
    +<span class="nc bnc" id="L330" title="All 2 branches missed.">                        String validStatus = status != null ? status : &quot;&quot;;</span>
    +<span class="nc" id="L331">                        statusLabel.setText(validStatus);</span>
    +
    +                        // Log UI state when status changes
    +<span class="nc" id="L334">                        log.info(</span>
    +                                &quot;UI State - Window visible: {}, Progress: {}%, Status: {}&quot;,
    +<span class="nc" id="L336">                                isVisible(), progressBar.getValue(), validStatus);</span>
    +
    +<span class="nc" id="L338">                        mainPanel.revalidate();</span>
    +<span class="nc" id="L339">                        mainPanel.repaint();</span>
    +<span class="nc" id="L340">                    } catch (Exception e) {</span>
    +<span class="nc" id="L341">                        log.error(&quot;Error updating status to: &quot; + status, e);</span>
    +<span class="nc" id="L342">                    }</span>
    +<span class="nc" id="L343">                });</span>
    +<span class="nc" id="L344">    }</span>
    +
    +    @Override
    +    public void dispose() {
    +<span class="nc" id="L348">        log.info(&quot;LoadingWindow disposing after {}ms&quot;, System.currentTimeMillis() - startTime);</span>
    +<span class="nc" id="L349">        super.dispose();</span>
    +<span class="nc" id="L350">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/index.html b/testResult/html/stirling.software.SPDF.UI.impl/index.html
    new file mode 100644
    index 000000000..bf97325bf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.UI.impl</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.UI.impl</span></div><h1>stirling.software.SPDF.UI.impl</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,577 of 1,577</td><td class="ctr2">0%</td><td class="bar">96 of 96</td><td class="ctr2">0%</td><td class="ctr1">96</td><td class="ctr2">96</td><td class="ctr1">429</td><td class="ctr2">429</td><td class="ctr1">46</td><td class="ctr2">46</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a7"><a href="LoadingWindow.html" class="el_class">LoadingWindow</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="786" alt="786"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="38" alt="38"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">29</td><td class="ctr2" id="g1">29</td><td class="ctr1" id="h0">173</td><td class="ctr2" id="i0">173</td><td class="ctr1" id="j1">10</td><td class="ctr2" id="k1">10</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="DesktopBrowser.html" class="el_class">DesktopBrowser</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="76" height="10" title="498" alt="498"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="94" height="10" title="30" alt="30"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">35</td><td class="ctr2" id="g0">35</td><td class="ctr1" id="h1">161</td><td class="ctr2" id="i1">161</td><td class="ctr1" id="j0">20</td><td class="ctr2" id="k0">20</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a2"><a href="DesktopBrowser$5.html" class="el_class">DesktopBrowser.new CefLoadHandlerAdapter() {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="150" alt="150"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="10" alt="10"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">9</td><td class="ctr2" id="g3">9</td><td class="ctr1" id="h2">52</td><td class="ctr2" id="i2">52</td><td class="ctr1" id="j2">4</td><td class="ctr2" id="k2">4</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="DesktopBrowser$3.html" class="el_class">DesktopBrowser.new ConsoleProgressHandler() {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="70" alt="70"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="10" alt="10"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">10</td><td class="ctr2" id="g2">10</td><td class="ctr1" id="h3">23</td><td class="ctr2" id="i3">23</td><td class="ctr1" id="j3">3</td><td class="ctr2" id="k3">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a1"><a href="DesktopBrowser$2.html" class="el_class">DesktopBrowser.new CefDownloadHandlerAdapter() {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="30" alt="30"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">5</td><td class="ctr2" id="g4">5</td><td class="ctr1" id="h4">8</td><td class="ctr2" id="i4">8</td><td class="ctr1" id="j4">3</td><td class="ctr2" id="k4">3</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a4"><a href="DesktopBrowser$1.html" class="el_class">DesktopBrowser.new MavenCefAppHandlerAdapter() {...}</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="17" alt="17"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h5">5</td><td class="ctr2" id="i5">5</td><td class="ctr1" id="j5">2</td><td class="ctr2" id="k5">2</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a6"><a href="DesktopBrowser$6.html" class="el_class">DesktopBrowser.new WindowStateListener() {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="14" alt="14"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">3</td><td class="ctr2" id="g6">3</td><td class="ctr1" id="h6">4</td><td class="ctr2" id="i6">4</td><td class="ctr1" id="j6">2</td><td class="ctr2" id="k6">2</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a5"><a href="DesktopBrowser$4.html" class="el_class">DesktopBrowser.new WindowAdapter() {...}</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="12" alt="12"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h7">4</td><td class="ctr2" id="i7">4</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.UI.impl/index.source.html b/testResult/html/stirling.software.SPDF.UI.impl/index.source.html
    new file mode 100644
    index 000000000..0fbd04112
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.UI.impl/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.UI.impl</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.UI.impl</span></div><h1>stirling.software.SPDF.UI.impl</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,577 of 1,577</td><td class="ctr2">0%</td><td class="bar">96 of 96</td><td class="ctr2">0%</td><td class="ctr1">96</td><td class="ctr2">96</td><td class="ctr1">429</td><td class="ctr2">429</td><td class="ctr1">46</td><td class="ctr2">46</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a0"><a href="DesktopBrowser.java.html" class="el_source">DesktopBrowser.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="791" alt="791"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="58" alt="58"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">67</td><td class="ctr2" id="g0">67</td><td class="ctr1" id="h0">256</td><td class="ctr2" id="i0">256</td><td class="ctr1" id="j0">36</td><td class="ctr2" id="k0">36</td><td class="ctr1" id="l0">7</td><td class="ctr2" id="m0">7</td></tr><tr><td id="a1"><a href="LoadingWindow.java.html" class="el_source">LoadingWindow.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="119" height="10" title="786" alt="786"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="78" height="10" title="38" alt="38"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">29</td><td class="ctr2" id="g1">29</td><td class="ctr1" id="h1">173</td><td class="ctr2" id="i1">173</td><td class="ctr1" id="j1">10</td><td class="ctr2" id="k1">10</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.interfaces/ShowAdminInterface.html b/testResult/html/stirling.software.SPDF.config.interfaces/ShowAdminInterface.html
    new file mode 100644
    index 000000000..1cb790e72
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.interfaces/ShowAdminInterface.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ShowAdminInterface</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.interfaces</a> &gt; <span class="el_class">ShowAdminInterface</span></div><h1>ShowAdminInterface</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ShowAdminInterface.java.html#L5" class="el_method">getShowUpdateOnlyAdmins()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.interfaces/ShowAdminInterface.java.html b/testResult/html/stirling.software.SPDF.config.interfaces/ShowAdminInterface.java.html
    new file mode 100644
    index 000000000..d0dedc7f0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.interfaces/ShowAdminInterface.java.html
    @@ -0,0 +1,8 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ShowAdminInterface.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.interfaces</a> &gt; <span class="el_source">ShowAdminInterface.java</span></div><h1>ShowAdminInterface.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.interfaces;
    +
    +public interface ShowAdminInterface {
    +    default boolean getShowUpdateOnlyAdmins() {
    +<span class="nc" id="L5">        return true;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.interfaces/index.html b/testResult/html/stirling.software.SPDF.config.interfaces/index.html
    new file mode 100644
    index 000000000..0e8e0f0b7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.interfaces/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.interfaces</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.interfaces</span></div><h1>stirling.software.SPDF.config.interfaces</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ShowAdminInterface.html" class="el_class">ShowAdminInterface</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.interfaces/index.source.html b/testResult/html/stirling.software.SPDF.config.interfaces/index.source.html
    new file mode 100644
    index 000000000..28abe58ab
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.interfaces/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.interfaces</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.interfaces</span></div><h1>stirling.software.SPDF.config.interfaces</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ShowAdminInterface.java.html" class="el_source">ShowAdminInterface.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.database/DatabaseConfig.html b/testResult/html/stirling.software.SPDF.config.security.database/DatabaseConfig.html
    new file mode 100644
    index 000000000..97b4805f5
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.database/DatabaseConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DatabaseConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.database</a> &gt; <span class="el_class">DatabaseConfig</span></div><h1>DatabaseConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">176 of 176</td><td class="ctr2">0%</td><td class="bar">11 of 11</td><td class="ctr2">0%</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">47</td><td class="ctr2">47</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a1"><a href="DatabaseConfig.java.html#L55" class="el_method">dataSource()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="78" alt="78"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">22</td><td class="ctr2" id="i0">22</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="DatabaseConfig.java.html#L123" class="el_method">getDriverClassName(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="40" alt="40"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="3" alt="3"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">12</td><td class="ctr2" id="i1">12</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="DatabaseConfig.java.html#L111" class="el_method">generateCustomDataSourceUrl(String, String, Integer, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="21" alt="21"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="DatabaseConfig.java.html#L34" class="el_method">DatabaseConfig(ApplicationProperties, boolean)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="18" alt="18"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">7</td><td class="ctr2" id="i2">7</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="DatabaseConfig.java.html#L92" class="el_method">useDefaultDataSource(DataSourceBuilder)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="15" alt="15"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a4"><a href="DatabaseConfig.java.html#L17" class="el_method">static {...}</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.database/DatabaseConfig.java.html b/testResult/html/stirling.software.SPDF.config.security.database/DatabaseConfig.java.html
    new file mode 100644
    index 000000000..4b61e7594
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.database/DatabaseConfig.java.html
    @@ -0,0 +1,147 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DatabaseConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.database</a> &gt; <span class="el_source">DatabaseConfig.java</span></div><h1>DatabaseConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.database;
    +
    +import javax.sql.DataSource;
    +
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.boot.jdbc.DataSourceBuilder;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +
    +import lombok.Getter;
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.exception.UnsupportedProviderException;
    +
    +<span class="nc" id="L17">@Slf4j</span>
    +@Getter
    +@Configuration
    +public class DatabaseConfig {
    +
    +    public final String DATASOURCE_DEFAULT_URL;
    +
    +    public static final String DATASOURCE_URL_TEMPLATE = &quot;jdbc:%s://%s:%4d/%s&quot;;
    +    public static final String DEFAULT_DRIVER = &quot;org.h2.Driver&quot;;
    +    public static final String DEFAULT_USERNAME = &quot;sa&quot;;
    +    public static final String POSTGRES_DRIVER = &quot;org.postgresql.Driver&quot;;
    +
    +    private final ApplicationProperties applicationProperties;
    +    private final boolean runningProOrHigher;
    +
    +    public DatabaseConfig(
    +            ApplicationProperties applicationProperties,
    +<span class="nc" id="L34">            @Qualifier(&quot;runningProOrHigher&quot;) boolean runningProOrHigher) {</span>
    +<span class="nc" id="L35">        DATASOURCE_DEFAULT_URL =</span>
    +                &quot;jdbc:h2:file:&quot;
    +<span class="nc" id="L37">                        + InstallationPathConfig.getConfigPath()</span>
    +                        + &quot;stirling-pdf-DB-2.3.232;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE&quot;;
    +<span class="nc" id="L39">        log.debug(&quot;Database URL: {}&quot;, DATASOURCE_DEFAULT_URL);</span>
    +<span class="nc" id="L40">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L41">        this.runningProOrHigher = runningProOrHigher;</span>
    +<span class="nc" id="L42">    }</span>
    +
    +    /**
    +     * Creates the &lt;code&gt;DataSource&lt;/code&gt; for the connection to the DB. If &lt;code&gt;useDefault&lt;/code&gt;
    +     * is set to &lt;code&gt;true&lt;/code&gt;, it will use the default H2 DB. If it is set to &lt;code&gt;false
    +     * &lt;/code&gt;, it will use the user's custom configuration set in the settings.yml.
    +     *
    +     * @return a &lt;code&gt;DataSource&lt;/code&gt; using the configuration settings in the settings.yml
    +     * @throws UnsupportedProviderException if the type of database selected is not supported
    +     */
    +    @Bean
    +    @Qualifier(&quot;dataSource&quot;)
    +    public DataSource dataSource() throws UnsupportedProviderException {
    +<span class="nc" id="L55">        DataSourceBuilder&lt;?&gt; dataSourceBuilder = DataSourceBuilder.create();</span>
    +
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">        if (!runningProOrHigher) {</span>
    +<span class="nc" id="L58">            return useDefaultDataSource(dataSourceBuilder);</span>
    +        }
    +
    +<span class="nc" id="L61">        ApplicationProperties.System system = applicationProperties.getSystem();</span>
    +<span class="nc" id="L62">        ApplicationProperties.Datasource datasource = system.getDatasource();</span>
    +
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">        if (!datasource.isEnableCustomDatabase()) {</span>
    +<span class="nc" id="L65">            return useDefaultDataSource(dataSourceBuilder);</span>
    +        }
    +
    +<span class="nc" id="L68">        log.info(&quot;Using custom database configuration&quot;);</span>
    +
    +<span class="nc bnc" id="L70" title="All 2 branches missed.">        if (!datasource.getCustomDatabaseUrl().isBlank()) {</span>
    +<span class="nc bnc" id="L71" title="All 2 branches missed.">            if (datasource.getCustomDatabaseUrl().contains(&quot;postgresql&quot;)) {</span>
    +<span class="nc" id="L72">                dataSourceBuilder.driverClassName(POSTGRES_DRIVER);</span>
    +            }
    +
    +<span class="nc" id="L75">            dataSourceBuilder.url(datasource.getCustomDatabaseUrl());</span>
    +        } else {
    +<span class="nc" id="L77">            dataSourceBuilder.driverClassName(getDriverClassName(datasource.getType()));</span>
    +<span class="nc" id="L78">            dataSourceBuilder.url(</span>
    +<span class="nc" id="L79">                    generateCustomDataSourceUrl(</span>
    +<span class="nc" id="L80">                            datasource.getType(),</span>
    +<span class="nc" id="L81">                            datasource.getHostName(),</span>
    +<span class="nc" id="L82">                            datasource.getPort(),</span>
    +<span class="nc" id="L83">                            datasource.getName()));</span>
    +        }
    +<span class="nc" id="L85">        dataSourceBuilder.username(datasource.getUsername());</span>
    +<span class="nc" id="L86">        dataSourceBuilder.password(datasource.getPassword());</span>
    +
    +<span class="nc" id="L88">        return dataSourceBuilder.build();</span>
    +    }
    +
    +    private DataSource useDefaultDataSource(DataSourceBuilder&lt;?&gt; dataSourceBuilder) {
    +<span class="nc" id="L92">        log.info(&quot;Using default H2 database&quot;);</span>
    +
    +<span class="nc" id="L94">        dataSourceBuilder.url(DATASOURCE_DEFAULT_URL);</span>
    +<span class="nc" id="L95">        dataSourceBuilder.username(DEFAULT_USERNAME);</span>
    +
    +<span class="nc" id="L97">        return dataSourceBuilder.build();</span>
    +    }
    +
    +    /**
    +     * Generate the URL the &lt;code&gt;DataSource&lt;/code&gt; will use to connect to the database
    +     *
    +     * @param dataSourceType the type of the database
    +     * @param hostname the host name
    +     * @param port the port number to use for the database
    +     * @param dataSourceName the name the database to connect to
    +     * @return the &lt;code&gt;DataSource&lt;/code&gt; URL
    +     */
    +    private String generateCustomDataSourceUrl(
    +            String dataSourceType, String hostname, Integer port, String dataSourceName) {
    +<span class="nc" id="L111">        return DATASOURCE_URL_TEMPLATE.formatted(dataSourceType, hostname, port, dataSourceName);</span>
    +    }
    +
    +    /**
    +     * Selects the database driver based on the type of database chosen.
    +     *
    +     * @param driverName the type of the driver (e.g. 'h2', 'postgresql')
    +     * @return the fully qualified driver for the database chosen
    +     * @throws UnsupportedProviderException when an unsupported database is selected
    +     */
    +    private String getDriverClassName(String driverName) throws UnsupportedProviderException {
    +        try {
    +<span class="nc" id="L123">            ApplicationProperties.Driver driver =</span>
    +<span class="nc" id="L124">                    ApplicationProperties.Driver.valueOf(driverName.toUpperCase());</span>
    +
    +<span class="nc bnc" id="L126" title="All 3 branches missed.">            switch (driver) {</span>
    +                case H2 -&gt; {
    +<span class="nc" id="L128">                    log.debug(&quot;H2 driver selected&quot;);</span>
    +<span class="nc" id="L129">                    return DEFAULT_DRIVER;</span>
    +                }
    +                case POSTGRESQL -&gt; {
    +<span class="nc" id="L132">                    log.debug(&quot;Postgres driver selected&quot;);</span>
    +<span class="nc" id="L133">                    return POSTGRES_DRIVER;</span>
    +                }
    +                default -&gt; {
    +<span class="nc" id="L136">                    log.warn(&quot;{} driver selected&quot;, driverName);</span>
    +<span class="nc" id="L137">                    throw new UnsupportedProviderException(</span>
    +                            driverName + &quot; is not currently supported&quot;);
    +                }
    +            }
    +<span class="nc" id="L141">        } catch (IllegalArgumentException e) {</span>
    +<span class="nc" id="L142">            log.warn(&quot;Unknown driver: {}&quot;, driverName);</span>
    +<span class="nc" id="L143">            throw new UnsupportedProviderException(driverName + &quot; is not currently supported&quot;);</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.database/DatabaseService.html b/testResult/html/stirling.software.SPDF.config.security.database/DatabaseService.html
    new file mode 100644
    index 000000000..0ecfa3d26
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.database/DatabaseService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DatabaseService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.database</a> &gt; <span class="el_class">DatabaseService</span></div><h1>DatabaseService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">551 of 551</td><td class="ctr2">0%</td><td class="bar">60 of 60</td><td class="ctr2">0%</td><td class="ctr1">49</td><td class="ctr2">49</td><td class="ctr1">154</td><td class="ctr2">154</td><td class="ctr1">19</td><td class="ctr2">19</td></tr></tfoot><tbody><tr><td id="a7"><a href="DatabaseService.java.html#L78" class="el_method">getBackupList()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="70" alt="70"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h0">23</td><td class="ctr2" id="i0">23</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="DatabaseService.java.html#L170" class="el_method">exportDatabase()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="114" height="10" title="67" alt="67"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h1">23</td><td class="ctr2" id="i1">23</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a14"><a href="DatabaseService.java.html#L305" class="el_method">isValidFileName(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="78" height="10" title="46" alt="46"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h4">11</td><td class="ctr2" id="i4">11</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="DatabaseService.java.html#L282" class="el_method">executeDatabaseScript(Path)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="42" alt="42"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h2">13</td><td class="ctr2" id="i2">13</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a8"><a href="DatabaseService.java.html#L221" class="el_method">getH2Version()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="63" height="10" title="37" alt="37"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h3">12</td><td class="ctr2" id="i3">12</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a12"><a href="DatabaseService.java.html#L143" class="el_method">importDatabaseFromUI(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="30" alt="30"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h8">7</td><td class="ctr2" id="i8">7</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a3"><a href="DatabaseService.java.html#L203" class="el_method">deleteOldestBackup(List)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="30" alt="30"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h5">9</td><td class="ctr2" id="i5">9</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a10"><a href="DatabaseService.java.html#L130" class="el_method">importDatabase()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="29" alt="29"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h10">6</td><td class="ctr2" id="i10">6</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a2"><a href="DatabaseService.java.html#L253" class="el_method">deleteBackupFile(String)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="29" alt="29"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h6">9</td><td class="ctr2" id="i6">9</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a0"><a href="DatabaseService.java.html#L118" class="el_method">createBackupDirectory()</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="44" height="10" title="26" alt="26"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h9">7</td><td class="ctr2" id="i9">7</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a11"><a href="DatabaseService.java.html#L157" class="el_method">importDatabaseFromUI(Path)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="44" height="10" title="26" alt="26"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h7">9</td><td class="ctr2" id="i7">9</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a1"><a href="DatabaseService.java.html#L47" class="el_method">DatabaseService(ApplicationProperties, DataSource)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="24" alt="24"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h11">6</td><td class="ctr2" id="i11">6</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a6"><a href="DatabaseService.java.html#L273" class="el_method">getBackupFilePath(String)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="20" alt="20"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f11">2</td><td class="ctr2" id="g11">2</td><td class="ctr1" id="h12">5</td><td class="ctr2" id="i12">5</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a9"><a href="DatabaseService.java.html#L62" class="el_method">hasBackup()</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="18" alt="18"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h13">4</td><td class="ctr2" id="i13">4</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a13"><a href="DatabaseService.java.html#L241" class="el_method">isH2Database()</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="18" alt="18"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f6">3</td><td class="ctr2" id="g6">3</td><td class="ctr1" id="h14">4</td><td class="ctr2" id="i14">4</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a17"><a href="DatabaseService.java.html#L87" class="el_method">lambda$getBackupList$0(Path)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="16" alt="16"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f7">3</td><td class="ctr2" id="g7">3</td><td class="ctr1" id="h15">4</td><td class="ctr2" id="i15">4</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a15"><a href="DatabaseService.java.html#L205" class="el_method">lambda$deleteOldestBackup$2(FileInfo)</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="10" alt="10"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h16">1</td><td class="ctr2" id="i16">1</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a16"><a href="DatabaseService.java.html#L172" class="el_method">lambda$exportDatabase$1(FileInfo)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="9" alt="9"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">2</td><td class="ctr2" id="g12">2</td><td class="ctr1" id="h17">1</td><td class="ctr2" id="i17">1</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a18"><a href="DatabaseService.java.html#L36" class="el_method">static {...}</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h18">1</td><td class="ctr2" id="i18">1</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.database/DatabaseService.java.html b/testResult/html/stirling.software.SPDF.config.security.database/DatabaseService.java.html
    new file mode 100644
    index 000000000..af85cccf3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.database/DatabaseService.java.html
    @@ -0,0 +1,318 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DatabaseService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.database</a> &gt; <span class="el_source">DatabaseService.java</span></div><h1>DatabaseService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.database;
    +
    +import java.io.IOException;
    +import java.nio.file.DirectoryStream;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.nio.file.attribute.BasicFileAttributes;
    +import java.sql.Connection;
    +import java.sql.PreparedStatement;
    +import java.sql.ResultSet;
    +import java.sql.SQLException;
    +import java.sql.Statement;
    +import java.time.LocalDateTime;
    +import java.time.ZoneId;
    +import java.time.format.DateTimeFormatter;
    +import java.util.ArrayList;
    +import java.util.Comparator;
    +import java.util.List;
    +import java.util.stream.Collectors;
    +
    +import javax.sql.DataSource;
    +
    +import org.springframework.jdbc.datasource.init.CannotReadScriptException;
    +import org.springframework.jdbc.datasource.init.ScriptException;
    +import org.springframework.stereotype.Service;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.config.interfaces.DatabaseInterface;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.exception.BackupNotFoundException;
    +import stirling.software.SPDF.utils.FileInfo;
    +
    +<span class="nc" id="L36">@Slf4j</span>
    +@Service
    +public class DatabaseService implements DatabaseInterface {
    +
    +    public static final String BACKUP_PREFIX = &quot;backup_&quot;;
    +    public static final String SQL_SUFFIX = &quot;.sql&quot;;
    +    private final Path BACKUP_DIR;
    +
    +    private final ApplicationProperties applicationProperties;
    +    private final DataSource dataSource;
    +
    +<span class="nc" id="L47">    public DatabaseService(ApplicationProperties applicationProperties, DataSource dataSource) {</span>
    +<span class="nc" id="L48">        this.BACKUP_DIR =</span>
    +<span class="nc" id="L49">                Paths.get(InstallationPathConfig.getConfigPath(), &quot;db&quot;, &quot;backup&quot;).normalize();</span>
    +<span class="nc" id="L50">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L51">        this.dataSource = dataSource;</span>
    +<span class="nc" id="L52">    }</span>
    +
    +    /**
    +     * Checks if there is at least one backup. First checks if the directory exists, then checks if
    +     * there are backup scripts within the directory
    +     *
    +     * @return true if there are backup scripts, false if there are not
    +     */
    +    @Override
    +    public boolean hasBackup() {
    +<span class="nc" id="L62">        createBackupDirectory();</span>
    +
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">        if (Files.exists(BACKUP_DIR)) {</span>
    +<span class="nc bnc" id="L65" title="All 2 branches missed.">            return !getBackupList().isEmpty();</span>
    +        }
    +
    +<span class="nc" id="L68">        return false;</span>
    +    }
    +
    +    /**
    +     * Read the backup directory and filter for files with the prefix &quot;backup_&quot; and suffix &quot;.sql&quot;
    +     *
    +     * @return a &lt;code&gt;List&lt;/code&gt; of backup files
    +     */
    +    @Override
    +    public List&lt;FileInfo&gt; getBackupList() {
    +<span class="nc" id="L78">        List&lt;FileInfo&gt; backupFiles = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L80" title="All 2 branches missed.">        if (isH2Database()) {</span>
    +<span class="nc" id="L81">            createBackupDirectory();</span>
    +
    +<span class="nc" id="L83">            try (DirectoryStream&lt;Path&gt; stream =</span>
    +<span class="nc" id="L84">                    Files.newDirectoryStream(</span>
    +                            BACKUP_DIR,
    +                            path -&gt;
    +<span class="nc bnc" id="L87" title="All 2 branches missed.">                                    path.getFileName().toString().startsWith(BACKUP_PREFIX)</span>
    +<span class="nc" id="L88">                                            &amp;&amp; path.getFileName()</span>
    +<span class="nc" id="L89">                                                    .toString()</span>
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">                                                    .endsWith(SQL_SUFFIX))) {</span>
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">                for (Path entry : stream) {</span>
    +<span class="nc" id="L92">                    BasicFileAttributes attrs =</span>
    +<span class="nc" id="L93">                            Files.readAttributes(entry, BasicFileAttributes.class);</span>
    +<span class="nc" id="L94">                    LocalDateTime modificationDate =</span>
    +<span class="nc" id="L95">                            LocalDateTime.ofInstant(</span>
    +<span class="nc" id="L96">                                    attrs.lastModifiedTime().toInstant(), ZoneId.systemDefault());</span>
    +<span class="nc" id="L97">                    LocalDateTime creationDate =</span>
    +<span class="nc" id="L98">                            LocalDateTime.ofInstant(</span>
    +<span class="nc" id="L99">                                    attrs.creationTime().toInstant(), ZoneId.systemDefault());</span>
    +<span class="nc" id="L100">                    long fileSize = attrs.size();</span>
    +<span class="nc" id="L101">                    backupFiles.add(</span>
    +                            new FileInfo(
    +<span class="nc" id="L103">                                    entry.getFileName().toString(),</span>
    +<span class="nc" id="L104">                                    entry.toString(),</span>
    +                                    modificationDate,
    +                                    fileSize,
    +                                    creationDate));
    +<span class="nc" id="L108">                }</span>
    +<span class="nc" id="L109">            } catch (IOException e) {</span>
    +<span class="nc" id="L110">                log.error(&quot;Error reading backup directory: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L111">            }</span>
    +        }
    +
    +<span class="nc" id="L114">        return backupFiles;</span>
    +    }
    +
    +    private void createBackupDirectory() {
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">        if (!Files.exists(BACKUP_DIR)) {</span>
    +            try {
    +<span class="nc" id="L120">                Files.createDirectories(BACKUP_DIR);</span>
    +<span class="nc" id="L121">                log.debug(&quot;create backup directory: {}&quot;, BACKUP_DIR);</span>
    +<span class="nc" id="L122">            } catch (IOException e) {</span>
    +<span class="nc" id="L123">                log.error(&quot;Error create backup directory: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L124">            }</span>
    +        }
    +<span class="nc" id="L126">    }</span>
    +
    +    @Override
    +    public void importDatabase() {
    +<span class="nc bnc" id="L130" title="All 2 branches missed.">        if (!hasBackup()) throw new BackupNotFoundException(&quot;No backup scripts were found.&quot;);</span>
    +
    +<span class="nc" id="L132">        List&lt;FileInfo&gt; backupList = this.getBackupList();</span>
    +<span class="nc" id="L133">        backupList.sort(Comparator.comparing(FileInfo::getModificationDate).reversed());</span>
    +
    +<span class="nc" id="L135">        Path latestExport = Paths.get(backupList.get(0).getFilePath());</span>
    +
    +<span class="nc" id="L137">        executeDatabaseScript(latestExport);</span>
    +<span class="nc" id="L138">    }</span>
    +
    +    /** Imports a database backup from the specified file. */
    +    public boolean importDatabaseFromUI(String fileName) {
    +        try {
    +<span class="nc" id="L143">            importDatabaseFromUI(getBackupFilePath(fileName));</span>
    +<span class="nc" id="L144">            return true;</span>
    +<span class="nc" id="L145">        } catch (IOException e) {</span>
    +<span class="nc" id="L146">            log.error(</span>
    +                    &quot;Error importing database from file: {}, message: {}&quot;,
    +                    fileName,
    +<span class="nc" id="L149">                    e.getMessage(),</span>
    +<span class="nc" id="L150">                    e.getCause());</span>
    +<span class="nc" id="L151">            return false;</span>
    +        }
    +    }
    +
    +    /** Imports a database backup from the specified path. */
    +    public boolean importDatabaseFromUI(Path tempTemplatePath) throws IOException {
    +<span class="nc" id="L157">        executeDatabaseScript(tempTemplatePath);</span>
    +<span class="nc" id="L158">        LocalDateTime dateNow = LocalDateTime.now();</span>
    +<span class="nc" id="L159">        DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern(&quot;yyyyMMddHHmm&quot;);</span>
    +<span class="nc" id="L160">        Path insertOutputFilePath =</span>
    +<span class="nc" id="L161">                this.getBackupFilePath(</span>
    +<span class="nc" id="L162">                        BACKUP_PREFIX + &quot;user_&quot; + dateNow.format(myFormatObj) + SQL_SUFFIX);</span>
    +<span class="nc" id="L163">        Files.copy(tempTemplatePath, insertOutputFilePath);</span>
    +<span class="nc" id="L164">        Files.deleteIfExists(tempTemplatePath);</span>
    +<span class="nc" id="L165">        return true;</span>
    +    }
    +
    +    @Override
    +    public void exportDatabase() {
    +<span class="nc" id="L170">        List&lt;FileInfo&gt; filteredBackupList =</span>
    +<span class="nc" id="L171">                this.getBackupList().stream()</span>
    +<span class="nc bnc" id="L172" title="All 2 branches missed.">                        .filter(backup -&gt; !backup.getFileName().startsWith(BACKUP_PREFIX + &quot;user_&quot;))</span>
    +<span class="nc" id="L173">                        .collect(Collectors.toList());</span>
    +
    +<span class="nc bnc" id="L175" title="All 2 branches missed.">        if (filteredBackupList.size() &gt; 5) {</span>
    +<span class="nc" id="L176">            deleteOldestBackup(filteredBackupList);</span>
    +        }
    +
    +<span class="nc" id="L179">        LocalDateTime dateNow = LocalDateTime.now();</span>
    +<span class="nc" id="L180">        DateTimeFormatter myFormatObj = DateTimeFormatter.ofPattern(&quot;yyyyMMddHHmm&quot;);</span>
    +<span class="nc" id="L181">        Path insertOutputFilePath =</span>
    +<span class="nc" id="L182">                this.getBackupFilePath(BACKUP_PREFIX + dateNow.format(myFormatObj) + SQL_SUFFIX);</span>
    +
    +<span class="nc bnc" id="L184" title="All 2 branches missed.">        if (isH2Database()) {</span>
    +<span class="nc" id="L185">            String query = &quot;SCRIPT SIMPLE COLUMNS DROP to ?;&quot;;</span>
    +
    +<span class="nc" id="L187">            try (Connection conn = dataSource.getConnection();</span>
    +<span class="nc" id="L188">                    PreparedStatement stmt = conn.prepareStatement(query)) {</span>
    +<span class="nc" id="L189">                stmt.setString(1, insertOutputFilePath.toString());</span>
    +<span class="nc" id="L190">                stmt.execute();</span>
    +<span class="nc" id="L191">            } catch (SQLException e) {</span>
    +<span class="nc" id="L192">                log.error(&quot;Error during database export: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L193">            } catch (CannotReadScriptException e) {</span>
    +<span class="nc" id="L194">                log.error(&quot;Error during database export: File {} not found&quot;, insertOutputFilePath);</span>
    +<span class="nc" id="L195">            }</span>
    +
    +<span class="nc" id="L197">            log.info(&quot;Database export completed: {}&quot;, insertOutputFilePath);</span>
    +        }
    +<span class="nc" id="L199">    }</span>
    +
    +    private static void deleteOldestBackup(List&lt;FileInfo&gt; filteredBackupList) {
    +        try {
    +<span class="nc" id="L203">            filteredBackupList.sort(</span>
    +<span class="nc" id="L204">                    Comparator.comparing(</span>
    +<span class="nc" id="L205">                            p -&gt; p.getFileName().substring(7, p.getFileName().length() - 4)));</span>
    +
    +<span class="nc" id="L207">            FileInfo oldestFile = filteredBackupList.get(0);</span>
    +<span class="nc" id="L208">            Files.deleteIfExists(Paths.get(oldestFile.getFilePath()));</span>
    +<span class="nc" id="L209">            log.info(&quot;Deleted oldest backup: {}&quot;, oldestFile.getFileName());</span>
    +<span class="nc" id="L210">        } catch (IOException e) {</span>
    +<span class="nc" id="L211">            log.error(&quot;Unable to delete oldest backup, message: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L212">        }</span>
    +<span class="nc" id="L213">    }</span>
    +
    +    /**
    +     * Retrieves the H2 database version.
    +     *
    +     * @return &lt;code&gt;String&lt;/code&gt; of the H2 version
    +     */
    +    public String getH2Version() {
    +<span class="nc" id="L221">        String version = &quot;Unknown&quot;;</span>
    +
    +<span class="nc bnc" id="L223" title="All 2 branches missed.">        if (isH2Database()) {</span>
    +<span class="nc" id="L224">            try (Connection conn = dataSource.getConnection()) {</span>
    +<span class="nc" id="L225">                try (Statement stmt = conn.createStatement();</span>
    +<span class="nc" id="L226">                        ResultSet rs = stmt.executeQuery(&quot;SELECT H2VERSION() AS version&quot;)) {</span>
    +<span class="nc bnc" id="L227" title="All 2 branches missed.">                    if (rs.next()) {</span>
    +<span class="nc" id="L228">                        version = rs.getString(&quot;version&quot;);</span>
    +<span class="nc" id="L229">                        log.info(&quot;H2 Database Version: {}&quot;, version);</span>
    +                    }
    +                }
    +<span class="nc" id="L232">            } catch (SQLException e) {</span>
    +<span class="nc" id="L233">                log.error(&quot;Error retrieving H2 version: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L234">            }</span>
    +        }
    +
    +<span class="nc" id="L237">        return version;</span>
    +    }
    +
    +    private boolean isH2Database() {
    +<span class="nc" id="L241">        ApplicationProperties.Datasource datasource =</span>
    +<span class="nc" id="L242">                applicationProperties.getSystem().getDatasource();</span>
    +<span class="nc bnc" id="L243" title="All 2 branches missed.">        return !datasource.isEnableCustomDatabase()</span>
    +<span class="nc bnc" id="L244" title="All 2 branches missed.">                || datasource.getType().equalsIgnoreCase(ApplicationProperties.Driver.H2.name());</span>
    +    }
    +
    +    /**
    +     * Deletes a backup file.
    +     *
    +     * @return true if successful, false if not
    +     */
    +    public boolean deleteBackupFile(String fileName) throws IOException {
    +<span class="nc bnc" id="L253" title="All 2 branches missed.">        if (!isValidFileName(fileName)) {</span>
    +<span class="nc" id="L254">            log.error(&quot;Invalid file name: {}&quot;, fileName);</span>
    +<span class="nc" id="L255">            return false;</span>
    +        }
    +<span class="nc" id="L257">        Path filePath = this.getBackupFilePath(fileName);</span>
    +<span class="nc bnc" id="L258" title="All 2 branches missed.">        if (Files.deleteIfExists(filePath)) {</span>
    +<span class="nc" id="L259">            log.info(&quot;Deleted backup file: {}&quot;, fileName);</span>
    +<span class="nc" id="L260">            return true;</span>
    +        } else {
    +<span class="nc" id="L262">            log.error(&quot;File not found or could not be deleted: {}&quot;, fileName);</span>
    +<span class="nc" id="L263">            return false;</span>
    +        }
    +    }
    +
    +    /**
    +     * Gets the Path for a given backup file name.
    +     *
    +     * @return the &lt;code&gt;Path&lt;/code&gt; object for the given file name
    +     */
    +    public Path getBackupFilePath(String fileName) {
    +<span class="nc" id="L273">        createBackupDirectory();</span>
    +<span class="nc" id="L274">        Path filePath = BACKUP_DIR.resolve(fileName).normalize();</span>
    +<span class="nc bnc" id="L275" title="All 2 branches missed.">        if (!filePath.startsWith(BACKUP_DIR)) {</span>
    +<span class="nc" id="L276">            throw new SecurityException(&quot;Path traversal detected&quot;);</span>
    +        }
    +<span class="nc" id="L278">        return filePath;</span>
    +    }
    +
    +    private void executeDatabaseScript(Path scriptPath) {
    +<span class="nc bnc" id="L282" title="All 2 branches missed.">        if (isH2Database()) {</span>
    +<span class="nc" id="L283">            String query = &quot;RUNSCRIPT from ?;&quot;;</span>
    +
    +<span class="nc" id="L285">            try (Connection conn = dataSource.getConnection();</span>
    +<span class="nc" id="L286">                    PreparedStatement stmt = conn.prepareStatement(query)) {</span>
    +<span class="nc" id="L287">                stmt.setString(1, scriptPath.toString());</span>
    +<span class="nc" id="L288">                stmt.execute();</span>
    +<span class="nc" id="L289">            } catch (SQLException e) {</span>
    +<span class="nc" id="L290">                log.error(&quot;Error during database import: {}&quot;, e.getMessage(), e);</span>
    +<span class="nc" id="L291">            } catch (ScriptException e) {</span>
    +<span class="nc" id="L292">                log.error(&quot;Error: File {} not found&quot;, scriptPath.toString(), e);</span>
    +<span class="nc" id="L293">            }</span>
    +        }
    +
    +<span class="nc" id="L296">        log.info(&quot;Database import completed: {}&quot;, scriptPath);</span>
    +<span class="nc" id="L297">    }</span>
    +
    +    /**
    +     * Checks for invalid characters or sequences
    +     *
    +     * @return true if it contains no invalid characters, false if it does
    +     */
    +    private boolean isValidFileName(String fileName) {
    +<span class="nc bnc" id="L305" title="All 2 branches missed.">        return fileName != null</span>
    +<span class="nc bnc" id="L306" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;..&quot;)</span>
    +<span class="nc bnc" id="L307" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;/&quot;)</span>
    +<span class="nc bnc" id="L308" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;\\&quot;)</span>
    +<span class="nc bnc" id="L309" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;:&quot;)</span>
    +<span class="nc bnc" id="L310" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;*&quot;)</span>
    +<span class="nc bnc" id="L311" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;?&quot;)</span>
    +<span class="nc bnc" id="L312" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;\&quot;&quot;)</span>
    +<span class="nc bnc" id="L313" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;&lt;&quot;)</span>
    +<span class="nc bnc" id="L314" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;&gt;&quot;)</span>
    +<span class="nc bnc" id="L315" title="All 2 branches missed.">                &amp;&amp; !fileName.contains(&quot;|&quot;);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.database/ScheduledTasks.html b/testResult/html/stirling.software.SPDF.config.security.database/ScheduledTasks.html
    new file mode 100644
    index 000000000..a150fb4af
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.database/ScheduledTasks.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ScheduledTasks</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.database</a> &gt; <span class="el_class">ScheduledTasks</span></div><h1>ScheduledTasks</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">10 of 10</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="ScheduledTasks.java.html#L19" class="el_method">ScheduledTasks(DatabaseInterface)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ScheduledTasks.java.html#L25" class="el_method">performBackup()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.database/ScheduledTasks.java.html b/testResult/html/stirling.software.SPDF.config.security.database/ScheduledTasks.java.html
    new file mode 100644
    index 000000000..91a2dd64a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.database/ScheduledTasks.java.html
    @@ -0,0 +1,28 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ScheduledTasks.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.database</a> &gt; <span class="el_source">ScheduledTasks.java</span></div><h1>ScheduledTasks.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.database;
    +
    +import java.sql.SQLException;
    +
    +import org.springframework.context.annotation.Conditional;
    +import org.springframework.scheduling.annotation.Scheduled;
    +import org.springframework.stereotype.Component;
    +
    +import stirling.software.SPDF.config.interfaces.DatabaseInterface;
    +import stirling.software.SPDF.controller.api.H2SQLCondition;
    +import stirling.software.SPDF.model.exception.UnsupportedProviderException;
    +
    +@Component
    +@Conditional(H2SQLCondition.class)
    +public class ScheduledTasks {
    +
    +    private final DatabaseInterface databaseService;
    +
    +<span class="nc" id="L19">    public ScheduledTasks(DatabaseInterface databaseService) {</span>
    +<span class="nc" id="L20">        this.databaseService = databaseService;</span>
    +<span class="nc" id="L21">    }</span>
    +
    +    @Scheduled(cron = &quot;0 0 0 * * ?&quot;)
    +    public void performBackup() throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L25">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L26">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.database/index.html b/testResult/html/stirling.software.SPDF.config.security.database/index.html
    new file mode 100644
    index 000000000..3c9dff589
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.database/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security.database</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security.database</span></div><h1>stirling.software.SPDF.config.security.database</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">737 of 737</td><td class="ctr2">0%</td><td class="bar">71 of 71</td><td class="ctr2">0%</td><td class="ctr1">63</td><td class="ctr2">63</td><td class="ctr1">206</td><td class="ctr2">206</td><td class="ctr1">27</td><td class="ctr2">27</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="DatabaseService.html" class="el_class">DatabaseService</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="551" alt="551"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="60" alt="60"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">49</td><td class="ctr2" id="g0">49</td><td class="ctr1" id="h0">154</td><td class="ctr2" id="i0">154</td><td class="ctr1" id="j0">19</td><td class="ctr2" id="k0">19</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="DatabaseConfig.html" class="el_class">DatabaseConfig</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="176" alt="176"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="11" alt="11"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">12</td><td class="ctr2" id="g1">12</td><td class="ctr1" id="h1">47</td><td class="ctr2" id="i1">47</td><td class="ctr1" id="j1">6</td><td class="ctr2" id="k1">6</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a2"><a href="ScheduledTasks.html" class="el_class">ScheduledTasks</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="10" alt="10"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j2">2</td><td class="ctr2" id="k2">2</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.database/index.source.html b/testResult/html/stirling.software.SPDF.config.security.database/index.source.html
    new file mode 100644
    index 000000000..12a7f155a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.database/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security.database</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security.database</span></div><h1>stirling.software.SPDF.config.security.database</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">737 of 737</td><td class="ctr2">0%</td><td class="bar">71 of 71</td><td class="ctr2">0%</td><td class="ctr1">63</td><td class="ctr2">63</td><td class="ctr1">206</td><td class="ctr2">206</td><td class="ctr1">27</td><td class="ctr2">27</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="DatabaseService.java.html" class="el_source">DatabaseService.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="551" alt="551"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="60" alt="60"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">49</td><td class="ctr2" id="g0">49</td><td class="ctr1" id="h0">154</td><td class="ctr2" id="i0">154</td><td class="ctr1" id="j0">19</td><td class="ctr2" id="k0">19</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="DatabaseConfig.java.html" class="el_source">DatabaseConfig.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="176" alt="176"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="11" alt="11"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">12</td><td class="ctr2" id="g1">12</td><td class="ctr1" id="h1">47</td><td class="ctr2" id="i1">47</td><td class="ctr1" id="j1">6</td><td class="ctr2" id="k1">6</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a2"><a href="ScheduledTasks.java.html" class="el_source">ScheduledTasks.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="10" alt="10"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j2">2</td><td class="ctr2" id="k2">2</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationFailureHandler.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationFailureHandler.html
    new file mode 100644
    index 000000000..1d029c9dc
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationFailureHandler.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomOAuth2AuthenticationFailureHandler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.oauth2</a> &gt; <span class="el_class">CustomOAuth2AuthenticationFailureHandler</span></div><h1>CustomOAuth2AuthenticationFailureHandler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">95 of 95</td><td class="ctr2">0%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="CustomOAuth2AuthenticationFailureHandler.java.html#L30" class="el_method">onAuthenticationFailure(HttpServletRequest, HttpServletResponse, AuthenticationException)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="88" alt="88"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h0">23</td><td class="ctr2" id="i0">23</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="CustomOAuth2AuthenticationFailureHandler.java.html#L19" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="CustomOAuth2AuthenticationFailureHandler.java.html#L20" class="el_method">CustomOAuth2AuthenticationFailureHandler()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationFailureHandler.java.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationFailureHandler.java.html
    new file mode 100644
    index 000000000..b9d13d699
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationFailureHandler.java.html
    @@ -0,0 +1,64 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomOAuth2AuthenticationFailureHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.oauth2</a> &gt; <span class="el_source">CustomOAuth2AuthenticationFailureHandler.java</span></div><h1>CustomOAuth2AuthenticationFailureHandler.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.oauth2;
    +
    +import java.io.IOException;
    +
    +import org.springframework.security.authentication.BadCredentialsException;
    +import org.springframework.security.authentication.DisabledException;
    +import org.springframework.security.authentication.LockedException;
    +import org.springframework.security.core.AuthenticationException;
    +import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
    +import org.springframework.security.oauth2.core.OAuth2Error;
    +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
    +
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +<span class="nc" id="L19">@Slf4j</span>
    +<span class="nc" id="L20">public class CustomOAuth2AuthenticationFailureHandler</span>
    +        extends SimpleUrlAuthenticationFailureHandler {
    +
    +    @Override
    +    public void onAuthenticationFailure(
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            AuthenticationException exception)
    +            throws IOException, ServletException {
    +
    +<span class="nc bnc" id="L30" title="All 2 branches missed.">        if (exception instanceof BadCredentialsException) {</span>
    +<span class="nc" id="L31">            log.error(&quot;BadCredentialsException&quot;, exception);</span>
    +<span class="nc" id="L32">            getRedirectStrategy().sendRedirect(request, response, &quot;/login?error=badCredentials&quot;);</span>
    +<span class="nc" id="L33">            return;</span>
    +        }
    +<span class="nc bnc" id="L35" title="All 2 branches missed.">        if (exception instanceof DisabledException) {</span>
    +<span class="nc" id="L36">            log.error(&quot;User is deactivated: &quot;, exception);</span>
    +<span class="nc" id="L37">            getRedirectStrategy().sendRedirect(request, response, &quot;/logout?userIsDisabled=true&quot;);</span>
    +<span class="nc" id="L38">            return;</span>
    +        }
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">        if (exception instanceof LockedException) {</span>
    +<span class="nc" id="L41">            log.error(&quot;Account locked: &quot;, exception);</span>
    +<span class="nc" id="L42">            getRedirectStrategy().sendRedirect(request, response, &quot;/logout?error=locked&quot;);</span>
    +<span class="nc" id="L43">            return;</span>
    +        }
    +<span class="nc bnc" id="L45" title="All 2 branches missed.">        if (exception instanceof OAuth2AuthenticationException oAuth2Exception) {</span>
    +<span class="nc" id="L46">            OAuth2Error error = oAuth2Exception.getError();</span>
    +
    +<span class="nc" id="L48">            String errorCode = error.getErrorCode();</span>
    +
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">            if (&quot;Password must not be null&quot;.equals(error.getErrorCode())) {</span>
    +<span class="nc" id="L51">                errorCode = &quot;userAlreadyExistsWeb&quot;;</span>
    +            }
    +
    +<span class="nc" id="L54">            log.error(</span>
    +                    &quot;OAuth2 Authentication error: {}&quot;,
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">                    errorCode != null ? errorCode : exception.getMessage(),</span>
    +                    exception);
    +<span class="nc" id="L58">            getRedirectStrategy().sendRedirect(request, response, &quot;/login?errorOAuth=&quot; + errorCode);</span>
    +        }
    +<span class="nc" id="L60">        log.error(&quot;Unhandled authentication exception&quot;, exception);</span>
    +<span class="nc" id="L61">        super.onAuthenticationFailure(request, response, exception);</span>
    +<span class="nc" id="L62">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationSuccessHandler.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationSuccessHandler.html
    new file mode 100644
    index 000000000..5fa56ba80
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationSuccessHandler.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomOAuth2AuthenticationSuccessHandler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.oauth2</a> &gt; <span class="el_class">CustomOAuth2AuthenticationSuccessHandler</span></div><h1>CustomOAuth2AuthenticationSuccessHandler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">157 of 157</td><td class="ctr2">0%</td><td class="bar">30 of 30</td><td class="ctr2">0%</td><td class="ctr1">17</td><td class="ctr2">17</td><td class="ctr1">45</td><td class="ctr2">45</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="CustomOAuth2AuthenticationSuccessHandler.java.html#L47" class="el_method">onAuthenticationSuccess(HttpServletRequest, HttpServletResponse, Authentication)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="145" alt="145"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="30" alt="30"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">16</td><td class="ctr2" id="g0">16</td><td class="ctr1" id="h0">40</td><td class="ctr2" id="i0">40</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CustomOAuth2AuthenticationSuccessHandler.java.html#L36" class="el_method">CustomOAuth2AuthenticationSuccessHandler(LoginAttemptService, ApplicationProperties, UserService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationSuccessHandler.java.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationSuccessHandler.java.html
    new file mode 100644
    index 000000000..229bf60fe
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2AuthenticationSuccessHandler.java.html
    @@ -0,0 +1,108 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomOAuth2AuthenticationSuccessHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.oauth2</a> &gt; <span class="el_source">CustomOAuth2AuthenticationSuccessHandler.java</span></div><h1>CustomOAuth2AuthenticationSuccessHandler.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.oauth2;
    +
    +import java.io.IOException;
    +import java.sql.SQLException;
    +
    +import org.springframework.security.authentication.LockedException;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.core.userdetails.UserDetails;
    +import org.springframework.security.oauth2.core.user.OAuth2User;
    +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
    +import org.springframework.security.web.savedrequest.SavedRequest;
    +
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +import jakarta.servlet.http.HttpSession;
    +
    +import stirling.software.SPDF.config.security.LoginAttemptService;
    +import stirling.software.SPDF.config.security.UserService;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
    +import stirling.software.SPDF.model.AuthenticationType;
    +import stirling.software.SPDF.model.exception.UnsupportedProviderException;
    +import stirling.software.SPDF.utils.RequestUriUtils;
    +
    +public class CustomOAuth2AuthenticationSuccessHandler
    +        extends SavedRequestAwareAuthenticationSuccessHandler {
    +
    +    private final LoginAttemptService loginAttemptService;
    +    private final ApplicationProperties applicationProperties;
    +    private final UserService userService;
    +
    +    public CustomOAuth2AuthenticationSuccessHandler(
    +            LoginAttemptService loginAttemptService,
    +            ApplicationProperties applicationProperties,
    +<span class="nc" id="L36">            UserService userService) {</span>
    +<span class="nc" id="L37">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L38">        this.userService = userService;</span>
    +<span class="nc" id="L39">        this.loginAttemptService = loginAttemptService;</span>
    +<span class="nc" id="L40">    }</span>
    +
    +    @Override
    +    public void onAuthenticationSuccess(
    +            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    +            throws ServletException, IOException {
    +
    +<span class="nc" id="L47">        Object principal = authentication.getPrincipal();</span>
    +<span class="nc" id="L48">        String username = &quot;&quot;;</span>
    +
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">        if (principal instanceof OAuth2User oAuth2User) {</span>
    +<span class="nc" id="L51">            username = oAuth2User.getName();</span>
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">        } else if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L53">            username = detailsUser.getUsername();</span>
    +        }
    +
    +        // Get the saved request
    +<span class="nc" id="L57">        HttpSession session = request.getSession(false);</span>
    +<span class="nc" id="L58">        String contextPath = request.getContextPath();</span>
    +        SavedRequest savedRequest =
    +<span class="nc bnc" id="L60" title="All 2 branches missed.">                (session != null)</span>
    +<span class="nc" id="L61">                        ? (SavedRequest) session.getAttribute(&quot;SPRING_SECURITY_SAVED_REQUEST&quot;)</span>
    +<span class="nc" id="L62">                        : null;</span>
    +
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">        if (savedRequest != null</span>
    +<span class="nc bnc" id="L65" title="All 2 branches missed.">                &amp;&amp; !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) {</span>
    +            // Redirect to the original destination
    +<span class="nc" id="L67">            super.onAuthenticationSuccess(request, response, authentication);</span>
    +        } else {
    +<span class="nc" id="L69">            OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();</span>
    +
    +<span class="nc bnc" id="L71" title="All 2 branches missed.">            if (loginAttemptService.isBlocked(username)) {</span>
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">                if (session != null) {</span>
    +<span class="nc" id="L73">                    session.removeAttribute(&quot;SPRING_SECURITY_SAVED_REQUEST&quot;);</span>
    +                }
    +<span class="nc" id="L75">                throw new LockedException(</span>
    +                        &quot;Your account has been locked due to too many failed login attempts.&quot;);
    +            }
    +
    +<span class="nc bnc" id="L79" title="All 2 branches missed.">            if (userService.isUserDisabled(username)) {</span>
    +<span class="nc" id="L80">                getRedirectStrategy()</span>
    +<span class="nc" id="L81">                        .sendRedirect(request, response, &quot;/logout?userIsDisabled=true&quot;);</span>
    +<span class="nc" id="L82">                return;</span>
    +            }
    +<span class="nc bnc" id="L84" title="All 2 branches missed.">            if (userService.usernameExistsIgnoreCase(username)</span>
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">                    &amp;&amp; userService.hasPassword(username)</span>
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">                    &amp;&amp; !userService.isAuthenticationTypeByUsername(username, AuthenticationType.SSO)</span>
    +<span class="nc bnc" id="L87" title="All 2 branches missed.">                    &amp;&amp; oAuth.getAutoCreateUser()) {</span>
    +<span class="nc" id="L88">                response.sendRedirect(contextPath + &quot;/logout?oAuth2AuthenticationErrorWeb=true&quot;);</span>
    +<span class="nc" id="L89">                return;</span>
    +            }
    +
    +            try {
    +<span class="nc bnc" id="L93" title="All 2 branches missed.">                if (oAuth.getBlockRegistration()</span>
    +<span class="nc bnc" id="L94" title="All 2 branches missed.">                        &amp;&amp; !userService.usernameExistsIgnoreCase(username)) {</span>
    +<span class="nc" id="L95">                    response.sendRedirect(contextPath + &quot;/logout?oAuth2AdminBlockedUser=true&quot;);</span>
    +<span class="nc" id="L96">                    return;</span>
    +                }
    +<span class="nc bnc" id="L98" title="All 2 branches missed.">                if (principal instanceof OAuth2User) {</span>
    +<span class="nc" id="L99">                    userService.processSSOPostLogin(username, oAuth.getAutoCreateUser());</span>
    +                }
    +<span class="nc" id="L101">                response.sendRedirect(contextPath + &quot;/&quot;);</span>
    +<span class="nc" id="L102">            } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {</span>
    +<span class="nc" id="L103">                response.sendRedirect(contextPath + &quot;/logout?invalidUsername=true&quot;);</span>
    +<span class="nc" id="L104">            }</span>
    +        }
    +<span class="nc" id="L106">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2UserService.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2UserService.html
    new file mode 100644
    index 000000000..960619048
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2UserService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomOAuth2UserService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.oauth2</a> &gt; <span class="el_class">CustomOAuth2UserService</span></div><h1>CustomOAuth2UserService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">113 of 113</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">30</td><td class="ctr2">30</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="CustomOAuth2UserService.java.html#L46" class="el_method">loadUser(OidcUserRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="92" alt="92"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">23</td><td class="ctr2" id="i0">23</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CustomOAuth2UserService.java.html#L26" class="el_method">CustomOAuth2UserService(ApplicationProperties, UserService, LoginAttemptService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="17" alt="17"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="CustomOAuth2UserService.java.html#L23" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2UserService.java.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2UserService.java.html
    new file mode 100644
    index 000000000..4a09df3a3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/CustomOAuth2UserService.java.html
    @@ -0,0 +1,84 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomOAuth2UserService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.oauth2</a> &gt; <span class="el_source">CustomOAuth2UserService.java</span></div><h1>CustomOAuth2UserService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.oauth2;
    +
    +import java.util.Optional;
    +
    +import org.springframework.security.authentication.LockedException;
    +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
    +import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
    +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
    +import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
    +import org.springframework.security.oauth2.core.OAuth2Error;
    +import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
    +import org.springframework.security.oauth2.core.oidc.user.OidcUser;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.LoginAttemptService;
    +import stirling.software.SPDF.config.security.UserService;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
    +import stirling.software.SPDF.model.User;
    +import stirling.software.SPDF.model.UsernameAttribute;
    +
    +<span class="nc" id="L23">@Slf4j</span>
    +public class CustomOAuth2UserService implements OAuth2UserService&lt;OidcUserRequest, OidcUser&gt; {
    +
    +<span class="nc" id="L26">    private final OidcUserService delegate = new OidcUserService();</span>
    +
    +    private final UserService userService;
    +
    +    private final LoginAttemptService loginAttemptService;
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    public CustomOAuth2UserService(
    +            ApplicationProperties applicationProperties,
    +            UserService userService,
    +<span class="nc" id="L37">            LoginAttemptService loginAttemptService) {</span>
    +<span class="nc" id="L38">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L39">        this.userService = userService;</span>
    +<span class="nc" id="L40">        this.loginAttemptService = loginAttemptService;</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    @Override
    +    public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
    +        try {
    +<span class="nc" id="L46">            OidcUser user = delegate.loadUser(userRequest);</span>
    +<span class="nc" id="L47">            OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();</span>
    +<span class="nc" id="L48">            UsernameAttribute usernameAttribute =</span>
    +<span class="nc" id="L49">                    UsernameAttribute.valueOf(oauth2.getUseAsUsername().toUpperCase());</span>
    +<span class="nc" id="L50">            String usernameAttributeKey = usernameAttribute.getName();</span>
    +
    +            // todo: save user by OIDC ID instead of username
    +<span class="nc" id="L53">            Optional&lt;User&gt; internalUser =</span>
    +<span class="nc" id="L54">                    userService.findByUsernameIgnoreCase(user.getAttribute(usernameAttributeKey));</span>
    +
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">            if (internalUser.isPresent()) {</span>
    +<span class="nc" id="L57">                String internalUsername = internalUser.get().getUsername();</span>
    +<span class="nc bnc" id="L58" title="All 2 branches missed.">                if (loginAttemptService.isBlocked(internalUsername)) {</span>
    +<span class="nc" id="L59">                    throw new LockedException(</span>
    +                            &quot;The account &quot;
    +                                    + internalUsername
    +                                    + &quot; has been locked due to too many failed login attempts.&quot;);
    +                }
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">                if (userService.hasPassword(usernameAttributeKey)) {</span>
    +<span class="nc" id="L65">                    throw new IllegalArgumentException(&quot;Password must not be null&quot;);</span>
    +                }
    +            }
    +
    +            // Return a new OidcUser with adjusted attributes
    +<span class="nc" id="L70">            return new DefaultOidcUser(</span>
    +<span class="nc" id="L71">                    user.getAuthorities(),</span>
    +<span class="nc" id="L72">                    userRequest.getIdToken(),</span>
    +<span class="nc" id="L73">                    user.getUserInfo(),</span>
    +                    usernameAttributeKey);
    +<span class="nc" id="L75">        } catch (IllegalArgumentException e) {</span>
    +<span class="nc" id="L76">            log.error(&quot;Error loading OIDC user: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L77">            throw new OAuth2AuthenticationException(new OAuth2Error(e.getMessage()), e);</span>
    +<span class="nc" id="L78">        } catch (Exception e) {</span>
    +<span class="nc" id="L79">            log.error(&quot;Unexpected error loading OIDC user&quot;, e);</span>
    +<span class="nc" id="L80">            throw new OAuth2AuthenticationException(&quot;Unexpected error during authentication&quot;);</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/OAuth2Configuration.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/OAuth2Configuration.html
    new file mode 100644
    index 000000000..2123e6795
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/OAuth2Configuration.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OAuth2Configuration</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.oauth2</a> &gt; <span class="el_class">OAuth2Configuration</span></div><h1>OAuth2Configuration</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">443 of 443</td><td class="ctr2">0%</td><td class="bar">36 of 36</td><td class="ctr2">0%</td><td class="ctr1">30</td><td class="ctr2">30</td><td class="ctr1">132</td><td class="ctr2">132</td><td class="ctr1">12</td><td class="ctr2">12</td></tr></tfoot><tbody><tr><td id="a9"><a href="OAuth2Configuration.java.html#L170" class="el_method">oidcClientRegistration()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="86" alt="86"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">25</td><td class="ctr2" id="i0">25</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="OAuth2Configuration.java.html#L104" class="el_method">googleClientRegistration()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="104" height="10" title="75" alt="75"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h1">25</td><td class="ctr2" id="i1">25</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="OAuth2Configuration.java.html#L137" class="el_method">githubClientRegistration()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="99" height="10" title="71" alt="71"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h2">25</td><td class="ctr2" id="i2">25</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a5"><a href="OAuth2Configuration.java.html#L74" class="el_method">keycloakClientRegistration()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="65" alt="65"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h3">22</td><td class="ctr2" id="i3">22</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="OAuth2Configuration.java.html#L59" class="el_method">clientRegistrationRepository()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="52" alt="52"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h5">9</td><td class="ctr2" id="i5">9</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="OAuth2Configuration.java.html#L230" class="el_method">lambda$userAuthoritiesMapper$0(Set, GrantedAuthority)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="48" alt="48"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h4">14</td><td class="ctr2" id="i4">14</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a7"><a href="OAuth2Configuration.java.html#L226" class="el_method">lambda$userAuthoritiesMapper$1(Collection)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="11" alt="11"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a4"><a href="OAuth2Configuration.java.html#L209" class="el_method">isOAuth2Enabled(ApplicationProperties.Security.OAUTH2)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="10" alt="10"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="OAuth2Configuration.java.html#L50" class="el_method">OAuth2Configuration(ApplicationProperties, UserService)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="9" alt="9"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h6">4</td><td class="ctr2" id="i6">4</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a3"><a href="OAuth2Configuration.java.html#L213" class="el_method">isClientInitialised(ApplicationProperties.Security.OAUTH2)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="9" alt="9"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a10"><a href="OAuth2Configuration.java.html#L39" class="el_method">static {...}</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a11"><a href="OAuth2Configuration.java.html#L225" class="el_method">userAuthoritiesMapper()</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">1</td><td class="ctr2" id="i11">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/OAuth2Configuration.java.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/OAuth2Configuration.java.html
    new file mode 100644
    index 000000000..0fe8b18f7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/OAuth2Configuration.java.html
    @@ -0,0 +1,253 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OAuth2Configuration.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.oauth2</a> &gt; <span class="el_source">OAuth2Configuration.java</span></div><h1>OAuth2Configuration.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.oauth2;
    +
    +import static org.springframework.security.oauth2.core.AuthorizationGrantType.AUTHORIZATION_CODE;
    +import static stirling.software.SPDF.utils.validation.Validator.*;
    +
    +import java.util.ArrayList;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Optional;
    +import java.util.Set;
    +
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.context.annotation.Lazy;
    +import org.springframework.security.core.GrantedAuthority;
    +import org.springframework.security.core.authority.SimpleGrantedAuthority;
    +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
    +import org.springframework.security.oauth2.client.registration.ClientRegistration;
    +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
    +import org.springframework.security.oauth2.client.registration.ClientRegistrations;
    +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
    +import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.UserService;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
    +import stirling.software.SPDF.model.User;
    +import stirling.software.SPDF.model.UsernameAttribute;
    +import stirling.software.SPDF.model.exception.NoProviderFoundException;
    +import stirling.software.SPDF.model.provider.GitHubProvider;
    +import stirling.software.SPDF.model.provider.GoogleProvider;
    +import stirling.software.SPDF.model.provider.KeycloakProvider;
    +import stirling.software.SPDF.model.provider.Provider;
    +
    +<span class="nc" id="L39">@Slf4j</span>
    +@Configuration
    +@ConditionalOnProperty(value = &quot;security.oauth2.enabled&quot;, havingValue = &quot;true&quot;)
    +public class OAuth2Configuration {
    +
    +    public static final String REDIRECT_URI_PATH = &quot;{baseUrl}/login/oauth2/code/&quot;;
    +
    +    private final ApplicationProperties applicationProperties;
    +    @Lazy private final UserService userService;
    +
    +    public OAuth2Configuration(
    +<span class="nc" id="L50">            ApplicationProperties applicationProperties, @Lazy UserService userService) {</span>
    +<span class="nc" id="L51">        this.userService = userService;</span>
    +<span class="nc" id="L52">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L53">    }</span>
    +
    +    @Bean
    +    @ConditionalOnProperty(value = &quot;security.oauth2.enabled&quot;, havingValue = &quot;true&quot;)
    +    public ClientRegistrationRepository clientRegistrationRepository()
    +            throws NoProviderFoundException {
    +<span class="nc" id="L59">        List&lt;ClientRegistration&gt; registrations = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L60">        githubClientRegistration().ifPresent(registrations::add);</span>
    +<span class="nc" id="L61">        oidcClientRegistration().ifPresent(registrations::add);</span>
    +<span class="nc" id="L62">        googleClientRegistration().ifPresent(registrations::add);</span>
    +<span class="nc" id="L63">        keycloakClientRegistration().ifPresent(registrations::add);</span>
    +
    +<span class="nc bnc" id="L65" title="All 2 branches missed.">        if (registrations.isEmpty()) {</span>
    +<span class="nc" id="L66">            log.error(&quot;No OAuth2 provider registered&quot;);</span>
    +<span class="nc" id="L67">            throw new NoProviderFoundException(&quot;At least one OAuth2 provider must be configured.&quot;);</span>
    +        }
    +
    +<span class="nc" id="L70">        return new InMemoryClientRegistrationRepository(registrations);</span>
    +    }
    +
    +    private Optional&lt;ClientRegistration&gt; keycloakClientRegistration() {
    +<span class="nc" id="L74">        OAUTH2 oauth2 = applicationProperties.getSecurity().getOauth2();</span>
    +
    +<span class="nc bnc" id="L76" title="All 4 branches missed.">        if (isOAuth2Enabled(oauth2) || isClientInitialised(oauth2)) {</span>
    +<span class="nc" id="L77">            return Optional.empty();</span>
    +        }
    +
    +<span class="nc" id="L80">        Client client = oauth2.getClient();</span>
    +<span class="nc" id="L81">        KeycloakProvider keycloakClient = client.getKeycloak();</span>
    +<span class="nc" id="L82">        Provider keycloak =</span>
    +                new KeycloakProvider(
    +<span class="nc" id="L84">                        keycloakClient.getIssuer(),</span>
    +<span class="nc" id="L85">                        keycloakClient.getClientId(),</span>
    +<span class="nc" id="L86">                        keycloakClient.getClientSecret(),</span>
    +<span class="nc" id="L87">                        keycloakClient.getScopes(),</span>
    +<span class="nc" id="L88">                        keycloakClient.getUseAsUsername());</span>
    +
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">        return validateProvider(keycloak)</span>
    +<span class="nc" id="L91">                ? Optional.of(</span>
    +<span class="nc" id="L92">                        ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())</span>
    +<span class="nc" id="L93">                                .registrationId(keycloak.getName())</span>
    +<span class="nc" id="L94">                                .clientId(keycloak.getClientId())</span>
    +<span class="nc" id="L95">                                .clientSecret(keycloak.getClientSecret())</span>
    +<span class="nc" id="L96">                                .scope(keycloak.getScopes())</span>
    +<span class="nc" id="L97">                                .userNameAttributeName(keycloak.getUseAsUsername().getName())</span>
    +<span class="nc" id="L98">                                .clientName(keycloak.getClientName())</span>
    +<span class="nc" id="L99">                                .build())</span>
    +<span class="nc" id="L100">                : Optional.empty();</span>
    +    }
    +
    +    private Optional&lt;ClientRegistration&gt; googleClientRegistration() {
    +<span class="nc" id="L104">        OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2();</span>
    +
    +<span class="nc bnc" id="L106" title="All 4 branches missed.">        if (isOAuth2Enabled(oAuth2) || isClientInitialised(oAuth2)) {</span>
    +<span class="nc" id="L107">            return Optional.empty();</span>
    +        }
    +
    +<span class="nc" id="L110">        Client client = oAuth2.getClient();</span>
    +<span class="nc" id="L111">        GoogleProvider googleClient = client.getGoogle();</span>
    +<span class="nc" id="L112">        Provider google =</span>
    +                new GoogleProvider(
    +<span class="nc" id="L114">                        googleClient.getClientId(),</span>
    +<span class="nc" id="L115">                        googleClient.getClientSecret(),</span>
    +<span class="nc" id="L116">                        googleClient.getScopes(),</span>
    +<span class="nc" id="L117">                        googleClient.getUseAsUsername());</span>
    +
    +<span class="nc bnc" id="L119" title="All 2 branches missed.">        return validateProvider(google)</span>
    +<span class="nc" id="L120">                ? Optional.of(</span>
    +<span class="nc" id="L121">                        ClientRegistration.withRegistrationId(google.getName())</span>
    +<span class="nc" id="L122">                                .clientId(google.getClientId())</span>
    +<span class="nc" id="L123">                                .clientSecret(google.getClientSecret())</span>
    +<span class="nc" id="L124">                                .scope(google.getScopes())</span>
    +<span class="nc" id="L125">                                .authorizationUri(google.getAuthorizationUri())</span>
    +<span class="nc" id="L126">                                .tokenUri(google.getTokenUri())</span>
    +<span class="nc" id="L127">                                .userInfoUri(google.getUserInfoUri())</span>
    +<span class="nc" id="L128">                                .userNameAttributeName(google.getUseAsUsername().getName())</span>
    +<span class="nc" id="L129">                                .clientName(google.getClientName())</span>
    +<span class="nc" id="L130">                                .redirectUri(REDIRECT_URI_PATH + google.getName())</span>
    +<span class="nc" id="L131">                                .authorizationGrantType(AUTHORIZATION_CODE)</span>
    +<span class="nc" id="L132">                                .build())</span>
    +<span class="nc" id="L133">                : Optional.empty();</span>
    +    }
    +
    +    private Optional&lt;ClientRegistration&gt; githubClientRegistration() {
    +<span class="nc" id="L137">        OAUTH2 oAuth2 = applicationProperties.getSecurity().getOauth2();</span>
    +
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">        if (isOAuth2Enabled(oAuth2)) {</span>
    +<span class="nc" id="L140">            return Optional.empty();</span>
    +        }
    +
    +<span class="nc" id="L143">        Client client = oAuth2.getClient();</span>
    +<span class="nc" id="L144">        GitHubProvider githubClient = client.getGithub();</span>
    +<span class="nc" id="L145">        Provider github =</span>
    +                new GitHubProvider(
    +<span class="nc" id="L147">                        githubClient.getClientId(),</span>
    +<span class="nc" id="L148">                        githubClient.getClientSecret(),</span>
    +<span class="nc" id="L149">                        githubClient.getScopes(),</span>
    +<span class="nc" id="L150">                        githubClient.getUseAsUsername());</span>
    +
    +<span class="nc bnc" id="L152" title="All 2 branches missed.">        return validateProvider(github)</span>
    +<span class="nc" id="L153">                ? Optional.of(</span>
    +<span class="nc" id="L154">                        ClientRegistration.withRegistrationId(github.getName())</span>
    +<span class="nc" id="L155">                                .clientId(github.getClientId())</span>
    +<span class="nc" id="L156">                                .clientSecret(github.getClientSecret())</span>
    +<span class="nc" id="L157">                                .scope(github.getScopes())</span>
    +<span class="nc" id="L158">                                .authorizationUri(github.getAuthorizationUri())</span>
    +<span class="nc" id="L159">                                .tokenUri(github.getTokenUri())</span>
    +<span class="nc" id="L160">                                .userInfoUri(github.getUserInfoUri())</span>
    +<span class="nc" id="L161">                                .userNameAttributeName(github.getUseAsUsername().getName())</span>
    +<span class="nc" id="L162">                                .clientName(github.getClientName())</span>
    +<span class="nc" id="L163">                                .redirectUri(REDIRECT_URI_PATH + github.getName())</span>
    +<span class="nc" id="L164">                                .authorizationGrantType(AUTHORIZATION_CODE)</span>
    +<span class="nc" id="L165">                                .build())</span>
    +<span class="nc" id="L166">                : Optional.empty();</span>
    +    }
    +
    +    private Optional&lt;ClientRegistration&gt; oidcClientRegistration() {
    +<span class="nc" id="L170">        OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();</span>
    +
    +<span class="nc bnc" id="L172" title="All 4 branches missed.">        if (isOAuth2Enabled(oauth) || isClientInitialised(oauth)) {</span>
    +<span class="nc" id="L173">            return Optional.empty();</span>
    +        }
    +
    +<span class="nc" id="L176">        String name = oauth.getProvider();</span>
    +<span class="nc" id="L177">        String firstChar = String.valueOf(name.charAt(0));</span>
    +<span class="nc" id="L178">        String clientName = name.replaceFirst(firstChar, firstChar.toUpperCase());</span>
    +
    +<span class="nc" id="L180">        Provider oidcProvider =</span>
    +                new Provider(
    +<span class="nc" id="L182">                        oauth.getIssuer(),</span>
    +                        name,
    +                        clientName,
    +<span class="nc" id="L185">                        oauth.getClientId(),</span>
    +<span class="nc" id="L186">                        oauth.getClientSecret(),</span>
    +<span class="nc" id="L187">                        oauth.getScopes(),</span>
    +<span class="nc" id="L188">                        UsernameAttribute.valueOf(oauth.getUseAsUsername().toUpperCase()),</span>
    +                        null,
    +                        null,
    +                        null);
    +
    +<span class="nc bnc" id="L193" title="All 4 branches missed.">        return !isStringEmpty(oidcProvider.getIssuer()) || validateProvider(oidcProvider)</span>
    +<span class="nc" id="L194">                ? Optional.of(</span>
    +<span class="nc" id="L195">                        ClientRegistrations.fromIssuerLocation(oauth.getIssuer())</span>
    +<span class="nc" id="L196">                                .registrationId(name)</span>
    +<span class="nc" id="L197">                                .clientId(oidcProvider.getClientId())</span>
    +<span class="nc" id="L198">                                .clientSecret(oidcProvider.getClientSecret())</span>
    +<span class="nc" id="L199">                                .scope(oidcProvider.getScopes())</span>
    +<span class="nc" id="L200">                                .userNameAttributeName(oidcProvider.getUseAsUsername().getName())</span>
    +<span class="nc" id="L201">                                .clientName(clientName)</span>
    +<span class="nc" id="L202">                                .redirectUri(REDIRECT_URI_PATH + &quot;oidc&quot;)</span>
    +<span class="nc" id="L203">                                .authorizationGrantType(AUTHORIZATION_CODE)</span>
    +<span class="nc" id="L204">                                .build())</span>
    +<span class="nc" id="L205">                : Optional.empty();</span>
    +    }
    +
    +    private boolean isOAuth2Enabled(OAUTH2 oAuth2) {
    +<span class="nc bnc" id="L209" title="All 4 branches missed.">        return oAuth2 == null || !oAuth2.getEnabled();</span>
    +    }
    +
    +    private boolean isClientInitialised(OAUTH2 oauth2) {
    +<span class="nc" id="L213">        Client client = oauth2.getClient();</span>
    +<span class="nc bnc" id="L214" title="All 2 branches missed.">        return client == null;</span>
    +    }
    +
    +    /*
    +    This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
    +    This is required for the internal; 'hasRole()' function to give out the correct role.
    +     */
    +
    +    @Bean
    +    @ConditionalOnProperty(value = &quot;security.oauth2.enabled&quot;, havingValue = &quot;true&quot;)
    +    GrantedAuthoritiesMapper userAuthoritiesMapper() {
    +<span class="nc" id="L225">        return (authorities) -&gt; {</span>
    +<span class="nc" id="L226">            Set&lt;GrantedAuthority&gt; mappedAuthorities = new HashSet&lt;&gt;();</span>
    +<span class="nc" id="L227">            authorities.forEach(</span>
    +                    authority -&gt; {
    +                        // Add existing OAUTH2 Authorities
    +<span class="nc" id="L230">                        mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));</span>
    +                        // Add Authorities from database for existing user, if user is present.
    +<span class="nc bnc" id="L232" title="All 2 branches missed.">                        if (authority instanceof OAuth2UserAuthority oAuth2Auth) {</span>
    +<span class="nc" id="L233">                            String useAsUsername =</span>
    +                                    applicationProperties
    +<span class="nc" id="L235">                                            .getSecurity()</span>
    +<span class="nc" id="L236">                                            .getOauth2()</span>
    +<span class="nc" id="L237">                                            .getUseAsUsername();</span>
    +<span class="nc" id="L238">                            Optional&lt;User&gt; userOpt =</span>
    +<span class="nc" id="L239">                                    userService.findByUsernameIgnoreCase(</span>
    +<span class="nc" id="L240">                                            (String) oAuth2Auth.getAttributes().get(useAsUsername));</span>
    +<span class="nc bnc" id="L241" title="All 2 branches missed.">                            if (userOpt.isPresent()) {</span>
    +<span class="nc" id="L242">                                User user = userOpt.get();</span>
    +<span class="nc" id="L243">                                mappedAuthorities.add(</span>
    +                                        new SimpleGrantedAuthority(
    +<span class="nc" id="L245">                                                userService.findRole(user).getAuthority()));</span>
    +                            }
    +                        }
    +<span class="nc" id="L248">                    });</span>
    +<span class="nc" id="L249">            return mappedAuthorities;</span>
    +        };
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/index.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/index.html
    new file mode 100644
    index 000000000..e05b62ade
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security.oauth2</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security.oauth2</span></div><h1>stirling.software.SPDF.config.security.oauth2</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">808 of 808</td><td class="ctr2">0%</td><td class="bar">84 of 84</td><td class="ctr2">0%</td><td class="ctr1">62</td><td class="ctr2">62</td><td class="ctr1">232</td><td class="ctr2">232</td><td class="ctr1">20</td><td class="ctr2">20</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a3"><a href="OAuth2Configuration.html" class="el_class">OAuth2Configuration</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="443" alt="443"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">30</td><td class="ctr2" id="g0">30</td><td class="ctr1" id="h0">132</td><td class="ctr2" id="i0">132</td><td class="ctr1" id="j0">12</td><td class="ctr2" id="k0">12</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a1"><a href="CustomOAuth2AuthenticationSuccessHandler.html" class="el_class">CustomOAuth2AuthenticationSuccessHandler</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="157" alt="157"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="30" alt="30"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">17</td><td class="ctr2" id="g1">17</td><td class="ctr1" id="h1">45</td><td class="ctr2" id="i1">45</td><td class="ctr1" id="j3">2</td><td class="ctr2" id="k3">2</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a2"><a href="CustomOAuth2UserService.html" class="el_class">CustomOAuth2UserService</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="113" alt="113"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">6</td><td class="ctr2" id="g3">6</td><td class="ctr1" id="h2">30</td><td class="ctr2" id="i2">30</td><td class="ctr1" id="j1">3</td><td class="ctr2" id="k1">3</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a0"><a href="CustomOAuth2AuthenticationFailureHandler.html" class="el_class">CustomOAuth2AuthenticationFailureHandler</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="95" alt="95"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="12" alt="12"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">9</td><td class="ctr2" id="g2">9</td><td class="ctr1" id="h3">25</td><td class="ctr2" id="i3">25</td><td class="ctr1" id="j2">3</td><td class="ctr2" id="k2">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.oauth2/index.source.html b/testResult/html/stirling.software.SPDF.config.security.oauth2/index.source.html
    new file mode 100644
    index 000000000..89f88f5b8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.oauth2/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security.oauth2</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security.oauth2</span></div><h1>stirling.software.SPDF.config.security.oauth2</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">808 of 808</td><td class="ctr2">0%</td><td class="bar">84 of 84</td><td class="ctr2">0%</td><td class="ctr1">62</td><td class="ctr2">62</td><td class="ctr1">232</td><td class="ctr2">232</td><td class="ctr1">20</td><td class="ctr2">20</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a3"><a href="OAuth2Configuration.java.html" class="el_source">OAuth2Configuration.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="443" alt="443"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">30</td><td class="ctr2" id="g0">30</td><td class="ctr1" id="h0">132</td><td class="ctr2" id="i0">132</td><td class="ctr1" id="j0">12</td><td class="ctr2" id="k0">12</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a1"><a href="CustomOAuth2AuthenticationSuccessHandler.java.html" class="el_source">CustomOAuth2AuthenticationSuccessHandler.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="157" alt="157"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="30" alt="30"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">17</td><td class="ctr2" id="g1">17</td><td class="ctr1" id="h1">45</td><td class="ctr2" id="i1">45</td><td class="ctr1" id="j3">2</td><td class="ctr2" id="k3">2</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a2"><a href="CustomOAuth2UserService.java.html" class="el_source">CustomOAuth2UserService.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="113" alt="113"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">6</td><td class="ctr2" id="g3">6</td><td class="ctr1" id="h2">30</td><td class="ctr2" id="i2">30</td><td class="ctr1" id="j1">3</td><td class="ctr2" id="k1">3</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a0"><a href="CustomOAuth2AuthenticationFailureHandler.java.html" class="el_source">CustomOAuth2AuthenticationFailureHandler.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="95" alt="95"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="12" alt="12"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">9</td><td class="ctr2" id="g2">9</td><td class="ctr1" id="h3">25</td><td class="ctr2" id="i3">25</td><td class="ctr1" id="j2">3</td><td class="ctr2" id="k2">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CertificateUtils.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CertificateUtils.html
    new file mode 100644
    index 000000000..1db4fef6a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CertificateUtils.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CertificateUtils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_class">CertificateUtils</span></div><h1>CertificateUtils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">90 of 90</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="CertificateUtils.java.html#L35" class="el_method">readPrivateKey(Resource)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="58" alt="58"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">11</td><td class="ctr2" id="i0">11</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="CertificateUtils.java.html#L23" class="el_method">readCertificate(Resource)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="29" alt="29"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="CertificateUtils.java.html#L20" class="el_method">CertificateUtils()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CertificateUtils.java.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CertificateUtils.java.html
    new file mode 100644
    index 000000000..8148522a7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CertificateUtils.java.html
    @@ -0,0 +1,57 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CertificateUtils.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_source">CertificateUtils.java</span></div><h1>CertificateUtils.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.saml2;
    +
    +import java.io.ByteArrayInputStream;
    +import java.io.InputStreamReader;
    +import java.nio.charset.StandardCharsets;
    +import java.security.cert.CertificateFactory;
    +import java.security.cert.X509Certificate;
    +import java.security.interfaces.RSAPrivateKey;
    +
    +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
    +import org.bouncycastle.openssl.PEMKeyPair;
    +import org.bouncycastle.openssl.PEMParser;
    +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
    +import org.bouncycastle.util.io.pem.PemObject;
    +import org.bouncycastle.util.io.pem.PemReader;
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    +import org.springframework.core.io.Resource;
    +
    +@ConditionalOnProperty(name = &quot;security.saml2.enabled&quot;, havingValue = &quot;true&quot;)
    +<span class="nc" id="L20">public class CertificateUtils {</span>
    +
    +    public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
    +<span class="nc" id="L23">        try (PemReader pemReader =</span>
    +                new PemReader(
    +                        new InputStreamReader(
    +<span class="nc" id="L26">                                certificateResource.getInputStream(), StandardCharsets.UTF_8))) {</span>
    +<span class="nc" id="L27">            PemObject pemObject = pemReader.readPemObject();</span>
    +<span class="nc" id="L28">            byte[] decodedCert = pemObject.getContent();</span>
    +<span class="nc" id="L29">            CertificateFactory cf = CertificateFactory.getInstance(&quot;X.509&quot;);</span>
    +<span class="nc" id="L30">            return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decodedCert));</span>
    +        }
    +    }
    +
    +    public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
    +<span class="nc" id="L35">        try (PEMParser pemParser =</span>
    +                new PEMParser(
    +                        new InputStreamReader(
    +<span class="nc" id="L38">                                privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) {</span>
    +
    +<span class="nc" id="L40">            Object object = pemParser.readObject();</span>
    +<span class="nc" id="L41">            JcaPEMKeyConverter converter = new JcaPEMKeyConverter();</span>
    +
    +<span class="nc bnc" id="L43" title="All 2 branches missed.">            if (object instanceof PEMKeyPair keypair) {</span>
    +                // Handle traditional RSA private key format
    +<span class="nc" id="L45">                return (RSAPrivateKey) converter.getPrivateKey(keypair.getPrivateKeyInfo());</span>
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">            } else if (object instanceof PrivateKeyInfo keyInfo) {</span>
    +                // Handle PKCS#8 format
    +<span class="nc" id="L48">                return (RSAPrivateKey) converter.getPrivateKey(keyInfo);</span>
    +            } else {
    +<span class="nc" id="L50">                throw new IllegalArgumentException(</span>
    +                        &quot;Unsupported key format: &quot;
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">                                + (object != null ? object.getClass().getName() : &quot;null&quot;));</span>
    +            }
    +<span class="nc" id="L54">        }</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticatedPrincipal.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticatedPrincipal.html
    new file mode 100644
    index 000000000..466d95561
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticatedPrincipal.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomSaml2AuthenticatedPrincipal</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_class">CustomSaml2AuthenticatedPrincipal</span></div><h1>CustomSaml2AuthenticatedPrincipal</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">21 of 21</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="CustomSaml2AuthenticatedPrincipal.java.html#L11" class="el_method">CustomSaml2AuthenticatedPrincipal(String, Map, String, List)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="15" alt="15"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="CustomSaml2AuthenticatedPrincipal.java.html#L20" class="el_method">getName()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="CustomSaml2AuthenticatedPrincipal.java.html#L25" class="el_method">getAttributes()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticatedPrincipal.java.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticatedPrincipal.java.html
    new file mode 100644
    index 000000000..ee80fab4d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticatedPrincipal.java.html
    @@ -0,0 +1,28 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomSaml2AuthenticatedPrincipal.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_source">CustomSaml2AuthenticatedPrincipal.java</span></div><h1>CustomSaml2AuthenticatedPrincipal.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.saml2;
    +
    +import java.io.Serializable;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
    +
    +@ConditionalOnProperty(name = &quot;security.saml2.enabled&quot;, havingValue = &quot;true&quot;)
    +<span class="nc" id="L11">public record CustomSaml2AuthenticatedPrincipal(</span>
    +        String name,
    +        Map&lt;String, List&lt;Object&gt;&gt; attributes,
    +        String nameId,
    +        List&lt;String&gt; sessionIndexes)
    +        implements Saml2AuthenticatedPrincipal, Serializable {
    +
    +    @Override
    +    public String getName() {
    +<span class="nc" id="L20">        return this.name;</span>
    +    }
    +
    +    @Override
    +    public Map&lt;String, List&lt;Object&gt;&gt; getAttributes() {
    +<span class="nc" id="L25">        return this.attributes;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationFailureHandler.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationFailureHandler.html
    new file mode 100644
    index 000000000..d149abca4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationFailureHandler.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomSaml2AuthenticationFailureHandler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_class">CustomSaml2AuthenticationFailureHandler</span></div><h1>CustomSaml2AuthenticationFailureHandler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">37 of 37</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="CustomSaml2AuthenticationFailureHandler.java.html#L27" class="el_method">onAuthenticationFailure(HttpServletRequest, HttpServletResponse, AuthenticationException)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="30" alt="30"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">9</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="CustomSaml2AuthenticationFailureHandler.java.html#L17" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="CustomSaml2AuthenticationFailureHandler.java.html#L19" class="el_method">CustomSaml2AuthenticationFailureHandler()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationFailureHandler.java.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationFailureHandler.java.html
    new file mode 100644
    index 000000000..6226ca992
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationFailureHandler.java.html
    @@ -0,0 +1,42 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomSaml2AuthenticationFailureHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_source">CustomSaml2AuthenticationFailureHandler.java</span></div><h1>CustomSaml2AuthenticationFailureHandler.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.saml2;
    +
    +import java.io.IOException;
    +
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    +import org.springframework.security.authentication.ProviderNotFoundException;
    +import org.springframework.security.core.AuthenticationException;
    +import org.springframework.security.saml2.core.Saml2Error;
    +import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
    +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +<span class="nc" id="L17">@Slf4j</span>
    +@ConditionalOnProperty(name = &quot;security.saml2.enabled&quot;, havingValue = &quot;true&quot;)
    +<span class="nc" id="L19">public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {</span>
    +
    +    @Override
    +    public void onAuthenticationFailure(
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            AuthenticationException exception)
    +            throws IOException {
    +<span class="nc" id="L27">        log.error(&quot;Authentication error&quot;, exception);</span>
    +
    +<span class="nc bnc" id="L29" title="All 2 branches missed.">        if (exception instanceof Saml2AuthenticationException) {</span>
    +<span class="nc" id="L30">            Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();</span>
    +<span class="nc" id="L31">            getRedirectStrategy()</span>
    +<span class="nc" id="L32">                    .sendRedirect(request, response, &quot;/login?errorOAuth=&quot; + error.getErrorCode());</span>
    +<span class="nc bnc" id="L33" title="All 2 branches missed.">        } else if (exception instanceof ProviderNotFoundException) {</span>
    +<span class="nc" id="L34">            getRedirectStrategy()</span>
    +<span class="nc" id="L35">                    .sendRedirect(</span>
    +                            request,
    +                            response,
    +                            &quot;/login?errorOAuth=not_authentication_provider_found&quot;);
    +        }
    +<span class="nc" id="L40">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationSuccessHandler.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationSuccessHandler.html
    new file mode 100644
    index 000000000..a440245a9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationSuccessHandler.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomSaml2AuthenticationSuccessHandler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_class">CustomSaml2AuthenticationSuccessHandler</span></div><h1>CustomSaml2AuthenticationSuccessHandler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">222 of 222</td><td class="ctr2">0%</td><td class="bar">36 of 36</td><td class="ctr2">0%</td><td class="ctr1">20</td><td class="ctr2">20</td><td class="ctr1">56</td><td class="ctr2">56</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="CustomSaml2AuthenticationSuccessHandler.java.html#L41" class="el_method">onAuthenticationSuccess(HttpServletRequest, HttpServletResponse, Authentication)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="218" alt="218"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">19</td><td class="ctr2" id="g0">19</td><td class="ctr1" id="h0">55</td><td class="ctr2" id="i0">55</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="CustomSaml2AuthenticationSuccessHandler.java.html#L28" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationSuccessHandler.java.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationSuccessHandler.java.html
    new file mode 100644
    index 000000000..409ef367d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2AuthenticationSuccessHandler.java.html
    @@ -0,0 +1,128 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomSaml2AuthenticationSuccessHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_source">CustomSaml2AuthenticationSuccessHandler.java</span></div><h1>CustomSaml2AuthenticationSuccessHandler.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.saml2;
    +
    +import java.io.IOException;
    +import java.sql.SQLException;
    +
    +import org.springframework.security.authentication.LockedException;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
    +import org.springframework.security.web.savedrequest.SavedRequest;
    +
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +import jakarta.servlet.http.HttpSession;
    +
    +import lombok.AllArgsConstructor;
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.LoginAttemptService;
    +import stirling.software.SPDF.config.security.UserService;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
    +import stirling.software.SPDF.model.AuthenticationType;
    +import stirling.software.SPDF.model.exception.UnsupportedProviderException;
    +import stirling.software.SPDF.utils.RequestUriUtils;
    +
    +@AllArgsConstructor
    +<span class="nc" id="L28">@Slf4j</span>
    +public class CustomSaml2AuthenticationSuccessHandler
    +        extends SavedRequestAwareAuthenticationSuccessHandler {
    +
    +    private LoginAttemptService loginAttemptService;
    +    private ApplicationProperties applicationProperties;
    +    private UserService userService;
    +
    +    @Override
    +    public void onAuthenticationSuccess(
    +            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    +            throws ServletException, IOException {
    +
    +<span class="nc" id="L41">        Object principal = authentication.getPrincipal();</span>
    +<span class="nc" id="L42">        log.debug(&quot;Starting SAML2 authentication success handling&quot;);</span>
    +
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">        if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2Principal) {</span>
    +<span class="nc" id="L45">            String username = saml2Principal.name();</span>
    +<span class="nc" id="L46">            log.debug(&quot;Authenticated principal found for user: {}&quot;, username);</span>
    +
    +<span class="nc" id="L48">            HttpSession session = request.getSession(false);</span>
    +<span class="nc" id="L49">            String contextPath = request.getContextPath();</span>
    +            SavedRequest savedRequest =
    +<span class="nc bnc" id="L51" title="All 2 branches missed.">                    (session != null)</span>
    +<span class="nc" id="L52">                            ? (SavedRequest) session.getAttribute(&quot;SPRING_SECURITY_SAVED_REQUEST&quot;)</span>
    +<span class="nc" id="L53">                            : null;</span>
    +
    +<span class="nc bnc" id="L55" title="All 2 branches missed.">            log.debug(</span>
    +                    &quot;Session exists: {}, Saved request exists: {}&quot;,
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">                    session != null,</span>
    +<span class="nc" id="L58">                    savedRequest != null);</span>
    +
    +<span class="nc bnc" id="L60" title="All 2 branches missed.">            if (savedRequest != null</span>
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">                    &amp;&amp; !RequestUriUtils.isStaticResource(</span>
    +<span class="nc" id="L62">                            contextPath, savedRequest.getRedirectUrl())) {</span>
    +<span class="nc" id="L63">                log.debug(</span>
    +                        &quot;Valid saved request found, redirecting to original destination: {}&quot;,
    +<span class="nc" id="L65">                        savedRequest.getRedirectUrl());</span>
    +<span class="nc" id="L66">                super.onAuthenticationSuccess(request, response, authentication);</span>
    +            } else {
    +<span class="nc" id="L68">                SAML2 saml2 = applicationProperties.getSecurity().getSaml2();</span>
    +<span class="nc" id="L69">                log.debug(</span>
    +                        &quot;Processing SAML2 authentication with autoCreateUser: {}&quot;,
    +<span class="nc" id="L71">                        saml2.getAutoCreateUser());</span>
    +
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">                if (loginAttemptService.isBlocked(username)) {</span>
    +<span class="nc" id="L74">                    log.debug(&quot;User {} is blocked due to too many login attempts&quot;, username);</span>
    +<span class="nc bnc" id="L75" title="All 2 branches missed.">                    if (session != null) {</span>
    +<span class="nc" id="L76">                        session.removeAttribute(&quot;SPRING_SECURITY_SAVED_REQUEST&quot;);</span>
    +                    }
    +<span class="nc" id="L78">                    throw new LockedException(</span>
    +                            &quot;Your account has been locked due to too many failed login attempts.&quot;);
    +                }
    +
    +<span class="nc" id="L82">                boolean userExists = userService.usernameExistsIgnoreCase(username);</span>
    +<span class="nc bnc" id="L83" title="All 4 branches missed.">                boolean hasPassword = userExists &amp;&amp; userService.hasPassword(username);</span>
    +<span class="nc bnc" id="L84" title="All 2 branches missed.">                boolean isSSOUser =</span>
    +                        userExists
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">                                &amp;&amp; userService.isAuthenticationTypeByUsername(</span>
    +                                        username, AuthenticationType.SSO);
    +
    +<span class="nc" id="L89">                log.debug(</span>
    +                        &quot;User status - Exists: {}, Has password: {}, Is SSO user: {}&quot;,
    +<span class="nc" id="L91">                        userExists,</span>
    +<span class="nc" id="L92">                        hasPassword,</span>
    +<span class="nc" id="L93">                        isSSOUser);</span>
    +
    +<span class="nc bnc" id="L95" title="All 8 branches missed.">                if (userExists &amp;&amp; hasPassword &amp;&amp; !isSSOUser &amp;&amp; saml2.getAutoCreateUser()) {</span>
    +<span class="nc" id="L96">                    log.debug(</span>
    +                            &quot;User {} exists with password but is not SSO user, redirecting to logout&quot;,
    +                            username);
    +<span class="nc" id="L99">                    response.sendRedirect(</span>
    +                            contextPath + &quot;/logout?oAuth2AuthenticationErrorWeb=true&quot;);
    +<span class="nc" id="L101">                    return;</span>
    +                }
    +
    +                try {
    +<span class="nc bnc" id="L105" title="All 4 branches missed.">                    if (saml2.getBlockRegistration() &amp;&amp; !userExists) {</span>
    +<span class="nc" id="L106">                        log.debug(&quot;Registration blocked for new user: {}&quot;, username);</span>
    +<span class="nc" id="L107">                        response.sendRedirect(</span>
    +                                contextPath + &quot;/login?errorOAuth=oAuth2AdminBlockedUser&quot;);
    +<span class="nc" id="L109">                        return;</span>
    +                    }
    +<span class="nc" id="L111">                    log.debug(&quot;Processing SSO post-login for user: {}&quot;, username);</span>
    +<span class="nc" id="L112">                    userService.processSSOPostLogin(username, saml2.getAutoCreateUser());</span>
    +<span class="nc" id="L113">                    log.debug(&quot;Successfully processed authentication for user: {}&quot;, username);</span>
    +<span class="nc" id="L114">                    response.sendRedirect(contextPath + &quot;/&quot;);</span>
    +<span class="nc" id="L115">                } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {</span>
    +<span class="nc" id="L116">                    log.debug(</span>
    +                            &quot;Invalid username detected for user: {}, redirecting to logout&quot;,
    +                            username);
    +<span class="nc" id="L119">                    response.sendRedirect(contextPath + &quot;/logout?invalidUsername=true&quot;);</span>
    +<span class="nc" id="L120">                }</span>
    +            }
    +<span class="nc" id="L122">        } else {</span>
    +<span class="nc" id="L123">            log.debug(&quot;Non-SAML2 principal detected, delegating to parent handler&quot;);</span>
    +<span class="nc" id="L124">            super.onAuthenticationSuccess(request, response, authentication);</span>
    +        }
    +<span class="nc" id="L126">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2ResponseAuthenticationConverter.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2ResponseAuthenticationConverter.html
    new file mode 100644
    index 000000000..41fe3ffa4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2ResponseAuthenticationConverter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomSaml2ResponseAuthenticationConverter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_class">CustomSaml2ResponseAuthenticationConverter</span></div><h1>CustomSaml2ResponseAuthenticationConverter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">265 of 265</td><td class="ctr2">0%</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="ctr1">23</td><td class="ctr2">23</td><td class="ctr1">52</td><td class="ctr2">52</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a0"><a href="CustomSaml2ResponseAuthenticationConverter.java.html#L62" class="el_method">convert(OpenSaml4AuthenticationProvider.ResponseToken)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="140" alt="140"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h0">28</td><td class="ctr2" id="i0">28</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="CustomSaml2ResponseAuthenticationConverter.java.html#L33" class="el_method">extractAttributes(Assertion)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="84" alt="84"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="102" height="10" title="12" alt="12"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">7</td><td class="ctr2" id="g1">7</td><td class="ctr1" id="h1">17</td><td class="ctr2" id="i1">17</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="CustomSaml2ResponseAuthenticationConverter.java.html#L113" class="el_method">getFirstAttributeValue(Map, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="17" alt="17"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="CustomSaml2ResponseAuthenticationConverter.java.html#L109" class="el_method">hasAttribute(Map, String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="14" alt="14"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="CustomSaml2ResponseAuthenticationConverter.java.html#L28" class="el_method">CustomSaml2ResponseAuthenticationConverter(UserService)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="CustomSaml2ResponseAuthenticationConverter.java.html#L21" class="el_method">static {...}</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2ResponseAuthenticationConverter.java.html b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2ResponseAuthenticationConverter.java.html
    new file mode 100644
    index 000000000..11cd18301
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/CustomSaml2ResponseAuthenticationConverter.java.html
    @@ -0,0 +1,117 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomSaml2ResponseAuthenticationConverter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_source">CustomSaml2ResponseAuthenticationConverter.java</span></div><h1>CustomSaml2ResponseAuthenticationConverter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.saml2;
    +
    +import java.util.*;
    +
    +import org.opensaml.core.xml.XMLObject;
    +import org.opensaml.saml.saml2.core.Assertion;
    +import org.opensaml.saml.saml2.core.Attribute;
    +import org.opensaml.saml.saml2.core.AttributeStatement;
    +import org.opensaml.saml.saml2.core.AuthnStatement;
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    +import org.springframework.core.convert.converter.Converter;
    +import org.springframework.security.core.authority.SimpleGrantedAuthority;
    +import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
    +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.UserService;
    +import stirling.software.SPDF.model.User;
    +
    +<span class="nc" id="L21">@Slf4j</span>
    +@ConditionalOnProperty(name = &quot;security.saml2.enabled&quot;, havingValue = &quot;true&quot;)
    +public class CustomSaml2ResponseAuthenticationConverter
    +        implements Converter&lt;ResponseToken, Saml2Authentication&gt; {
    +
    +    private final UserService userService;
    +
    +<span class="nc" id="L28">    public CustomSaml2ResponseAuthenticationConverter(UserService userService) {</span>
    +<span class="nc" id="L29">        this.userService = userService;</span>
    +<span class="nc" id="L30">    }</span>
    +
    +    private Map&lt;String, List&lt;Object&gt;&gt; extractAttributes(Assertion assertion) {
    +<span class="nc" id="L33">        Map&lt;String, List&lt;Object&gt;&gt; attributes = new HashMap&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L35" title="All 2 branches missed.">        for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {</span>
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">            for (Attribute attribute : attributeStatement.getAttributes()) {</span>
    +<span class="nc" id="L37">                String attributeName = attribute.getName();</span>
    +<span class="nc" id="L38">                List&lt;Object&gt; values = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">                for (XMLObject xmlObject : attribute.getAttributeValues()) {</span>
    +                    // Get the text content directly
    +<span class="nc" id="L42">                    String value = xmlObject.getDOM().getTextContent();</span>
    +<span class="nc bnc" id="L43" title="All 4 branches missed.">                    if (value != null &amp;&amp; !value.trim().isEmpty()) {</span>
    +<span class="nc" id="L44">                        values.add(value);</span>
    +                    }
    +<span class="nc" id="L46">                }</span>
    +
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">                if (!values.isEmpty()) {</span>
    +                    // Store with both full URI and last part of the URI
    +<span class="nc" id="L50">                    attributes.put(attributeName, values);</span>
    +<span class="nc" id="L51">                    String shortName = attributeName.substring(attributeName.lastIndexOf('/') + 1);</span>
    +<span class="nc" id="L52">                    attributes.put(shortName, values);</span>
    +                }
    +<span class="nc" id="L54">            }</span>
    +<span class="nc" id="L55">        }</span>
    +
    +<span class="nc" id="L57">        return attributes;</span>
    +    }
    +
    +    @Override
    +    public Saml2Authentication convert(ResponseToken responseToken) {
    +<span class="nc" id="L62">        Assertion assertion = responseToken.getResponse().getAssertions().get(0);</span>
    +<span class="nc" id="L63">        Map&lt;String, List&lt;Object&gt;&gt; attributes = extractAttributes(assertion);</span>
    +
    +        // Debug log with actual values
    +<span class="nc" id="L66">        log.debug(&quot;Extracted SAML Attributes: {}&quot;, attributes);</span>
    +
    +        // Try to get username/identifier in order of preference
    +        String userIdentifier;
    +<span class="nc bnc" id="L70" title="All 2 branches missed.">        if (hasAttribute(attributes, &quot;username&quot;)) {</span>
    +<span class="nc" id="L71">            userIdentifier = getFirstAttributeValue(attributes, &quot;username&quot;);</span>
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">        } else if (hasAttribute(attributes, &quot;emailaddress&quot;)) {</span>
    +<span class="nc" id="L73">            userIdentifier = getFirstAttributeValue(attributes, &quot;emailaddress&quot;);</span>
    +<span class="nc bnc" id="L74" title="All 2 branches missed.">        } else if (hasAttribute(attributes, &quot;name&quot;)) {</span>
    +<span class="nc" id="L75">            userIdentifier = getFirstAttributeValue(attributes, &quot;name&quot;);</span>
    +<span class="nc bnc" id="L76" title="All 2 branches missed.">        } else if (hasAttribute(attributes, &quot;upn&quot;)) {</span>
    +<span class="nc" id="L77">            userIdentifier = getFirstAttributeValue(attributes, &quot;upn&quot;);</span>
    +<span class="nc bnc" id="L78" title="All 2 branches missed.">        } else if (hasAttribute(attributes, &quot;uid&quot;)) {</span>
    +<span class="nc" id="L79">            userIdentifier = getFirstAttributeValue(attributes, &quot;uid&quot;);</span>
    +        } else {
    +<span class="nc" id="L81">            userIdentifier = assertion.getSubject().getNameID().getValue();</span>
    +        }
    +
    +        // Rest of your existing code...
    +<span class="nc" id="L85">        Optional&lt;User&gt; userOpt = userService.findByUsernameIgnoreCase(userIdentifier);</span>
    +<span class="nc" id="L86">        SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(&quot;ROLE_USER&quot;);</span>
    +<span class="nc bnc" id="L87" title="All 2 branches missed.">        if (userOpt.isPresent()) {</span>
    +<span class="nc" id="L88">            User user = userOpt.get();</span>
    +<span class="nc" id="L89">            simpleGrantedAuthority =</span>
    +<span class="nc" id="L90">                    new SimpleGrantedAuthority(userService.findRole(user).getAuthority());</span>
    +        }
    +
    +<span class="nc" id="L93">        List&lt;String&gt; sessionIndexes = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L94" title="All 2 branches missed.">        for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {</span>
    +<span class="nc" id="L95">            sessionIndexes.add(authnStatement.getSessionIndex());</span>
    +<span class="nc" id="L96">        }</span>
    +
    +<span class="nc" id="L98">        CustomSaml2AuthenticatedPrincipal principal =</span>
    +                new CustomSaml2AuthenticatedPrincipal(
    +                        userIdentifier, attributes, userIdentifier, sessionIndexes);
    +
    +<span class="nc" id="L102">        return new Saml2Authentication(</span>
    +                principal,
    +<span class="nc" id="L104">                responseToken.getToken().getSaml2Response(),</span>
    +<span class="nc" id="L105">                List.of(simpleGrantedAuthority));</span>
    +    }
    +
    +    private boolean hasAttribute(Map&lt;String, List&lt;Object&gt;&gt; attributes, String name) {
    +<span class="nc bnc" id="L109" title="All 4 branches missed.">        return attributes.containsKey(name) &amp;&amp; !attributes.get(name).isEmpty();</span>
    +    }
    +
    +    private String getFirstAttributeValue(Map&lt;String, List&lt;Object&gt;&gt; attributes, String name) {
    +<span class="nc" id="L113">        List&lt;Object&gt; values = attributes.get(name);</span>
    +<span class="nc bnc" id="L114" title="All 4 branches missed.">        return values != null &amp;&amp; !values.isEmpty() ? values.get(0).toString() : null;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/SAML2Configuration.html b/testResult/html/stirling.software.SPDF.config.security.saml2/SAML2Configuration.html
    new file mode 100644
    index 000000000..441989fa4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/SAML2Configuration.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SAML2Configuration</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_class">SAML2Configuration</span></div><h1>SAML2Configuration</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">287 of 287</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">79</td><td class="ctr2">79</td><td class="ctr1">11</td><td class="ctr2">11</td></tr></tfoot><tbody><tr><td id="a8"><a href="SAML2Configuration.java.html#L43" class="el_method">relyingPartyRegistrations()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="68" alt="68"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h0">20</td><td class="ctr2" id="i0">20</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="SAML2Configuration.java.html#L90" class="el_method">lambda$authenticationRequestResolver$3(OpenSaml4AuthenticationRequestResolver.AuthnRequestContext)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="109" height="10" title="62" alt="62"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h1">19</td><td class="ctr2" id="i1">19</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a7"><a href="SAML2Configuration.java.html#L148" class="el_method">logHttpRequestDetails(HttpServletRequest)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="97" height="10" title="55" alt="55"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">13</td><td class="ctr2" id="i2">13</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="SAML2Configuration.java.html#L126" class="el_method">logAuthnRequestDetails(AuthnRequest)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="43" alt="43"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h3">9</td><td class="ctr2" id="i3">9</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="SAML2Configuration.java.html#L65" class="el_method">lambda$relyingPartyRegistrations$2(ApplicationProperties.Security.SAML2, Saml2X509Credential, AssertingPartyMetadata.Builder)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="21" alt="21"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">9</td><td class="ctr2" id="i4">9</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="SAML2Configuration.java.html#L85" class="el_method">authenticationRequestResolver(RelyingPartyRegistrationRepository)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="10" alt="10"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="SAML2Configuration.java.html#L152" class="el_method">lambda$logHttpRequestDetails$4(HttpServletRequest, String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="8" alt="8"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a9"><a href="SAML2Configuration.java.html#L36" class="el_method">SAML2Configuration(ApplicationProperties)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="6" alt="6"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a4"><a href="SAML2Configuration.java.html#L67" class="el_method">lambda$relyingPartyRegistrations$1(Saml2X509Credential, Collection)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="5" alt="5"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a3"><a href="SAML2Configuration.java.html#L55" class="el_method">lambda$relyingPartyRegistrations$0(Saml2X509Credential, Collection)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="5" alt="5"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a10"><a href="SAML2Configuration.java.html#L30" class="el_method">static {...}</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="4" alt="4"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/SAML2Configuration.java.html b/testResult/html/stirling.software.SPDF.config.security.saml2/SAML2Configuration.java.html
    new file mode 100644
    index 000000000..21ea92076
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/SAML2Configuration.java.html
    @@ -0,0 +1,177 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SAML2Configuration.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.saml2</a> &gt; <span class="el_source">SAML2Configuration.java</span></div><h1>SAML2Configuration.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.saml2;
    +
    +import java.security.cert.X509Certificate;
    +import java.util.Collections;
    +import java.util.UUID;
    +
    +import org.opensaml.saml.saml2.core.AuthnRequest;
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.core.io.Resource;
    +import org.springframework.security.saml2.core.Saml2X509Credential;
    +import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
    +import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
    +import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
    +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
    +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
    +import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
    +import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
    +import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
    +
    +@Configuration
    +<span class="nc" id="L30">@Slf4j</span>
    +@ConditionalOnProperty(value = &quot;security.saml2.enabled&quot;, havingValue = &quot;true&quot;)
    +public class SAML2Configuration {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L36">    public SAML2Configuration(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L37">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L38">    }</span>
    +
    +    @Bean
    +    @ConditionalOnProperty(name = &quot;security.saml2.enabled&quot;, havingValue = &quot;true&quot;)
    +    public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
    +<span class="nc" id="L43">        SAML2 samlConf = applicationProperties.getSecurity().getSaml2();</span>
    +<span class="nc" id="L44">        X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getIdpCert());</span>
    +<span class="nc" id="L45">        Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);</span>
    +<span class="nc" id="L46">        Resource privateKeyResource = samlConf.getPrivateKey();</span>
    +<span class="nc" id="L47">        Resource certificateResource = samlConf.getSpCert();</span>
    +<span class="nc" id="L48">        Saml2X509Credential signingCredential =</span>
    +                new Saml2X509Credential(
    +<span class="nc" id="L50">                        CertificateUtils.readPrivateKey(privateKeyResource),</span>
    +<span class="nc" id="L51">                        CertificateUtils.readCertificate(certificateResource),</span>
    +                        Saml2X509CredentialType.SIGNING);
    +<span class="nc" id="L53">        RelyingPartyRegistration rp =</span>
    +<span class="nc" id="L54">                RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())</span>
    +<span class="nc" id="L55">                        .signingX509Credentials(c -&gt; c.add(signingCredential))</span>
    +<span class="nc" id="L56">                        .entityId(samlConf.getIdpIssuer())</span>
    +<span class="nc" id="L57">                        .singleLogoutServiceBinding(Saml2MessageBinding.POST)</span>
    +<span class="nc" id="L58">                        .singleLogoutServiceLocation(samlConf.getIdpSingleLogoutUrl())</span>
    +<span class="nc" id="L59">                        .singleLogoutServiceResponseLocation(&quot;http://localhost:8080/login&quot;)</span>
    +<span class="nc" id="L60">                        .assertionConsumerServiceBinding(Saml2MessageBinding.POST)</span>
    +<span class="nc" id="L61">                        .assertionConsumerServiceLocation(</span>
    +                                &quot;{baseUrl}/login/saml2/sso/{registrationId}&quot;)
    +<span class="nc" id="L63">                        .assertingPartyMetadata(</span>
    +                                metadata -&gt;
    +<span class="nc" id="L65">                                        metadata.entityId(samlConf.getIdpIssuer())</span>
    +<span class="nc" id="L66">                                                .verificationX509Credentials(</span>
    +<span class="nc" id="L67">                                                        c -&gt; c.add(verificationCredential))</span>
    +<span class="nc" id="L68">                                                .singleSignOnServiceBinding(</span>
    +                                                        Saml2MessageBinding.POST)
    +<span class="nc" id="L70">                                                .singleSignOnServiceLocation(</span>
    +<span class="nc" id="L71">                                                        samlConf.getIdpSingleLoginUrl())</span>
    +<span class="nc" id="L72">                                                .singleLogoutServiceBinding(</span>
    +                                                        Saml2MessageBinding.POST)
    +<span class="nc" id="L74">                                                .singleLogoutServiceLocation(</span>
    +<span class="nc" id="L75">                                                        samlConf.getIdpSingleLogoutUrl())</span>
    +<span class="nc" id="L76">                                                .wantAuthnRequestsSigned(true))</span>
    +<span class="nc" id="L77">                        .build();</span>
    +<span class="nc" id="L78">        return new InMemoryRelyingPartyRegistrationRepository(rp);</span>
    +    }
    +
    +    @Bean
    +    @ConditionalOnProperty(name = &quot;security.saml2.enabled&quot;, havingValue = &quot;true&quot;)
    +    public OpenSaml4AuthenticationRequestResolver authenticationRequestResolver(
    +            RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) {
    +<span class="nc" id="L85">        OpenSaml4AuthenticationRequestResolver resolver =</span>
    +                new OpenSaml4AuthenticationRequestResolver(relyingPartyRegistrationRepository);
    +
    +<span class="nc" id="L88">        resolver.setAuthnRequestCustomizer(</span>
    +                customizer -&gt; {
    +<span class="nc" id="L90">                    HttpServletRequest request = customizer.getRequest();</span>
    +<span class="nc" id="L91">                    AuthnRequest authnRequest = customizer.getAuthnRequest();</span>
    +<span class="nc" id="L92">                    HttpSessionSaml2AuthenticationRequestRepository requestRepository =</span>
    +                            new HttpSessionSaml2AuthenticationRequestRepository();
    +<span class="nc" id="L94">                    AbstractSaml2AuthenticationRequest saml2AuthenticationRequest =</span>
    +<span class="nc" id="L95">                            requestRepository.loadAuthenticationRequest(request);</span>
    +
    +<span class="nc bnc" id="L97" title="All 2 branches missed.">                    if (saml2AuthenticationRequest != null) {</span>
    +<span class="nc" id="L98">                        String sessionId = request.getSession(false).getId();</span>
    +
    +<span class="nc" id="L100">                        log.debug(</span>
    +                                &quot;Retrieving SAML 2 authentication request ID from the current HTTP session {}&quot;,
    +                                sessionId);
    +
    +<span class="nc" id="L104">                        String authenticationRequestId = saml2AuthenticationRequest.getId();</span>
    +
    +<span class="nc bnc" id="L106" title="All 2 branches missed.">                        if (!authenticationRequestId.isBlank()) {</span>
    +<span class="nc" id="L107">                            authnRequest.setID(authenticationRequestId);</span>
    +                        } else {
    +<span class="nc" id="L109">                            log.warn(</span>
    +                                    &quot;No authentication request found for HTTP session {}. Generating new ID&quot;,
    +                                    sessionId);
    +<span class="nc" id="L112">                            authnRequest.setID(&quot;ARQ&quot; + UUID.randomUUID().toString().substring(1));</span>
    +                        }
    +<span class="nc" id="L114">                    } else {</span>
    +<span class="nc" id="L115">                        log.debug(&quot;Generating new authentication request ID&quot;);</span>
    +<span class="nc" id="L116">                        authnRequest.setID(&quot;ARQ&quot; + UUID.randomUUID().toString().substring(1));</span>
    +                    }
    +
    +<span class="nc" id="L119">                    logAuthnRequestDetails(authnRequest);</span>
    +<span class="nc" id="L120">                    logHttpRequestDetails(request);</span>
    +<span class="nc" id="L121">                });</span>
    +<span class="nc" id="L122">        return resolver;</span>
    +    }
    +
    +    private static void logAuthnRequestDetails(AuthnRequest authnRequest) {
    +<span class="nc" id="L126">        String message =</span>
    +                &quot;&quot;&quot;
    +                        AuthnRequest:
    +
    +                        ID: {}
    +                        Issuer: {}
    +                        IssueInstant: {}
    +                        AssertionConsumerService (ACS) URL: {}
    +                        &quot;&quot;&quot;;
    +<span class="nc" id="L135">        log.debug(</span>
    +                message,
    +<span class="nc" id="L137">                authnRequest.getID(),</span>
    +<span class="nc bnc" id="L138" title="All 2 branches missed.">                authnRequest.getIssuer() != null ? authnRequest.getIssuer().getValue() : null,</span>
    +<span class="nc" id="L139">                authnRequest.getIssueInstant(),</span>
    +<span class="nc" id="L140">                authnRequest.getAssertionConsumerServiceURL());</span>
    +
    +<span class="nc bnc" id="L142" title="All 2 branches missed.">        if (authnRequest.getNameIDPolicy() != null) {</span>
    +<span class="nc" id="L143">            log.debug(&quot;NameIDPolicy Format: {}&quot;, authnRequest.getNameIDPolicy().getFormat());</span>
    +        }
    +<span class="nc" id="L145">    }</span>
    +
    +    private static void logHttpRequestDetails(HttpServletRequest request) {
    +<span class="nc" id="L148">        log.debug(&quot;HTTP Headers: &quot;);</span>
    +<span class="nc" id="L149">        Collections.list(request.getHeaderNames())</span>
    +<span class="nc" id="L150">                .forEach(</span>
    +                        headerName -&gt;
    +<span class="nc" id="L152">                                log.debug(&quot;{}: {}&quot;, headerName, request.getHeader(headerName)));</span>
    +<span class="nc" id="L153">        String message =</span>
    +                &quot;&quot;&quot;
    +                        HTTP Request Method: {}
    +                        Session ID: {}
    +                        Request Path: {}
    +                        Query String: {}
    +                        Remote Address: {}
    +
    +                        SAML Request Parameters:
    +
    +                        SAMLRequest: {}
    +                        RelayState: {}
    +                        &quot;&quot;&quot;;
    +<span class="nc" id="L166">        log.debug(</span>
    +                message,
    +<span class="nc" id="L168">                request.getMethod(),</span>
    +<span class="nc" id="L169">                request.getSession().getId(),</span>
    +<span class="nc" id="L170">                request.getRequestURI(),</span>
    +<span class="nc" id="L171">                request.getQueryString(),</span>
    +<span class="nc" id="L172">                request.getRemoteAddr(),</span>
    +<span class="nc" id="L173">                request.getParameter(&quot;SAMLRequest&quot;),</span>
    +<span class="nc" id="L174">                request.getParameter(&quot;RelayState&quot;));</span>
    +<span class="nc" id="L175">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/index.html b/testResult/html/stirling.software.SPDF.config.security.saml2/index.html
    new file mode 100644
    index 000000000..b8d1c120d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security.saml2</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security.saml2</span></div><h1>stirling.software.SPDF.config.security.saml2</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">922 of 922</td><td class="ctr2">0%</td><td class="bar">88 of 88</td><td class="ctr2">0%</td><td class="ctr1">72</td><td class="ctr2">72</td><td class="ctr1">219</td><td class="ctr2">219</td><td class="ctr1">28</td><td class="ctr2">28</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a5"><a href="SAML2Configuration.html" class="el_class">SAML2Configuration</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="287" alt="287"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f2">15</td><td class="ctr2" id="g2">15</td><td class="ctr1" id="h0">79</td><td class="ctr2" id="i0">79</td><td class="ctr1" id="j0">11</td><td class="ctr2" id="k0">11</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a4"><a href="CustomSaml2ResponseAuthenticationConverter.html" class="el_class">CustomSaml2ResponseAuthenticationConverter</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="110" height="10" title="265" alt="265"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="113" height="10" title="34" alt="34"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">23</td><td class="ctr2" id="g0">23</td><td class="ctr1" id="h2">52</td><td class="ctr2" id="i2">52</td><td class="ctr1" id="j1">6</td><td class="ctr2" id="k1">6</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a3"><a href="CustomSaml2AuthenticationSuccessHandler.html" class="el_class">CustomSaml2AuthenticationSuccessHandler</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="92" height="10" title="222" alt="222"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">20</td><td class="ctr2" id="g1">20</td><td class="ctr1" id="h1">56</td><td class="ctr2" id="i1">56</td><td class="ctr1" id="j5">2</td><td class="ctr2" id="k5">2</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a0"><a href="CertificateUtils.html" class="el_class">CertificateUtils</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="90" alt="90"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">6</td><td class="ctr2" id="g3">6</td><td class="ctr1" id="h3">18</td><td class="ctr2" id="i3">18</td><td class="ctr1" id="j2">3</td><td class="ctr2" id="k2">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a2"><a href="CustomSaml2AuthenticationFailureHandler.html" class="el_class">CustomSaml2AuthenticationFailureHandler</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="37" alt="37"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">5</td><td class="ctr2" id="g4">5</td><td class="ctr1" id="h4">11</td><td class="ctr2" id="i4">11</td><td class="ctr1" id="j3">3</td><td class="ctr2" id="k3">3</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a1"><a href="CustomSaml2AuthenticatedPrincipal.html" class="el_class">CustomSaml2AuthenticatedPrincipal</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="21" alt="21"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j4">3</td><td class="ctr2" id="k4">3</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.saml2/index.source.html b/testResult/html/stirling.software.SPDF.config.security.saml2/index.source.html
    new file mode 100644
    index 000000000..232b8232a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.saml2/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security.saml2</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security.saml2</span></div><h1>stirling.software.SPDF.config.security.saml2</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">922 of 922</td><td class="ctr2">0%</td><td class="bar">88 of 88</td><td class="ctr2">0%</td><td class="ctr1">72</td><td class="ctr2">72</td><td class="ctr1">219</td><td class="ctr2">219</td><td class="ctr1">28</td><td class="ctr2">28</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a5"><a href="SAML2Configuration.java.html" class="el_source">SAML2Configuration.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="287" alt="287"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f2">15</td><td class="ctr2" id="g2">15</td><td class="ctr1" id="h0">79</td><td class="ctr2" id="i0">79</td><td class="ctr1" id="j0">11</td><td class="ctr2" id="k0">11</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a4"><a href="CustomSaml2ResponseAuthenticationConverter.java.html" class="el_source">CustomSaml2ResponseAuthenticationConverter.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="110" height="10" title="265" alt="265"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="113" height="10" title="34" alt="34"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">23</td><td class="ctr2" id="g0">23</td><td class="ctr1" id="h2">52</td><td class="ctr2" id="i2">52</td><td class="ctr1" id="j1">6</td><td class="ctr2" id="k1">6</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a3"><a href="CustomSaml2AuthenticationSuccessHandler.java.html" class="el_source">CustomSaml2AuthenticationSuccessHandler.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="92" height="10" title="222" alt="222"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">20</td><td class="ctr2" id="g1">20</td><td class="ctr1" id="h1">56</td><td class="ctr2" id="i1">56</td><td class="ctr1" id="j5">2</td><td class="ctr2" id="k5">2</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a0"><a href="CertificateUtils.java.html" class="el_source">CertificateUtils.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="90" alt="90"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">6</td><td class="ctr2" id="g3">6</td><td class="ctr1" id="h3">18</td><td class="ctr2" id="i3">18</td><td class="ctr1" id="j2">3</td><td class="ctr2" id="k2">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a2"><a href="CustomSaml2AuthenticationFailureHandler.java.html" class="el_source">CustomSaml2AuthenticationFailureHandler.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="37" alt="37"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">5</td><td class="ctr2" id="g4">5</td><td class="ctr1" id="h4">11</td><td class="ctr2" id="i4">11</td><td class="ctr1" id="j3">3</td><td class="ctr2" id="k3">3</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a1"><a href="CustomSaml2AuthenticatedPrincipal.java.html" class="el_source">CustomSaml2AuthenticatedPrincipal.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="21" alt="21"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j4">3</td><td class="ctr2" id="k4">3</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/CustomHttpSessionListener.html b/testResult/html/stirling.software.SPDF.config.security.session/CustomHttpSessionListener.html
    new file mode 100644
    index 000000000..bb9bcc1e2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/CustomHttpSessionListener.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomHttpSessionListener</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_class">CustomHttpSessionListener</span></div><h1>CustomHttpSessionListener</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">18 of 18</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="CustomHttpSessionListener.java.html#L28" class="el_method">sessionDestroyed(HttpSessionEvent)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CustomHttpSessionListener.java.html#L19" class="el_method">CustomHttpSessionListener(SessionPersistentRegistry)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="102" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="CustomHttpSessionListener.java.html#L12" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="CustomHttpSessionListener.java.html#L24" class="el_method">sessionCreated(HttpSessionEvent)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="1" alt="1"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/CustomHttpSessionListener.java.html b/testResult/html/stirling.software.SPDF.config.security.session/CustomHttpSessionListener.java.html
    new file mode 100644
    index 000000000..f0db4716f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/CustomHttpSessionListener.java.html
    @@ -0,0 +1,31 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomHttpSessionListener.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_source">CustomHttpSessionListener.java</span></div><h1>CustomHttpSessionListener.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.session;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.stereotype.Component;
    +
    +import jakarta.servlet.http.HttpSessionEvent;
    +import jakarta.servlet.http.HttpSessionListener;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +@Component
    +<span class="nc" id="L12">@Slf4j</span>
    +public class CustomHttpSessionListener implements HttpSessionListener {
    +
    +    private SessionPersistentRegistry sessionPersistentRegistry;
    +
    +    @Autowired
    +    public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) {
    +<span class="nc" id="L19">        super();</span>
    +<span class="nc" id="L20">        this.sessionPersistentRegistry = sessionPersistentRegistry;</span>
    +<span class="nc" id="L21">    }</span>
    +
    +    @Override
    +<span class="nc" id="L24">    public void sessionCreated(HttpSessionEvent se) {}</span>
    +
    +    @Override
    +    public void sessionDestroyed(HttpSessionEvent se) {
    +<span class="nc" id="L28">        sessionPersistentRegistry.expireSession(se.getSession().getId());</span>
    +<span class="nc" id="L29">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry$1.html b/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry$1.html
    new file mode 100644
    index 000000000..b133812e6
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SessionPersistentRegistry.new Comparator() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_class">SessionPersistentRegistry.new Comparator() {...}</span></div><h1>SessionPersistentRegistry.new Comparator() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="SessionPersistentRegistry.java.html#L181" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="SessionPersistentRegistry.java.html#L185" class="el_method">compare(SessionEntity, SessionEntity)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry.html b/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry.html
    new file mode 100644
    index 000000000..9687bc452
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SessionPersistentRegistry</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_class">SessionPersistentRegistry</span></div><h1>SessionPersistentRegistry</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">300 of 300</td><td class="ctr2">0%</td><td class="bar">36 of 36</td><td class="ctr2">0%</td><td class="ctr1">32</td><td class="ctr2">32</td><td class="ctr1">80</td><td class="ctr2">80</td><td class="ctr1">14</td><td class="ctr2">14</td></tr></tfoot><tbody><tr><td id="a4"><a href="SessionPersistentRegistry.java.html#L43" class="el_method">getAllSessions(Object, boolean)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="81" alt="81"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">21</td><td class="ctr2" id="i0">21</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a10"><a href="SessionPersistentRegistry.java.html#L75" class="el_method">registerNewSession(String, Object)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="97" height="10" title="66" alt="66"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h1">17</td><td class="ctr2" id="i1">17</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="SessionPersistentRegistry.java.html#L32" class="el_method">getAllPrincipals()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="26" alt="26"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h3">6</td><td class="ctr2" id="i3">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a8"><a href="SessionPersistentRegistry.java.html#L124" class="el_method">getSessionInformation(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="24" alt="24"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h2">8</td><td class="ctr2" id="i2">8</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a9"><a href="SessionPersistentRegistry.java.html#L114" class="el_method">refreshLastRequest(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="23" alt="23"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h4">6</td><td class="ctr2" id="i4">6</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="SessionPersistentRegistry.java.html#L173" class="el_method">findLatestSession(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="22" alt="22"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h6">5</td><td class="ctr2" id="i6">5</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a0"><a href="SessionPersistentRegistry.java.html#L147" class="el_method">expireSession(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="21" alt="21"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a13"><a href="SessionPersistentRegistry.java.html#L168" class="el_method">updateSessionByPrincipalName(String, boolean, Date)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a12"><a href="SessionPersistentRegistry.java.html#L26" class="el_method">SessionPersistentRegistry(SessionRepository)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="6" alt="6"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a11"><a href="SessionPersistentRegistry.java.html#L108" class="el_method">removeSessionInformation(String)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a5"><a href="SessionPersistentRegistry.java.html#L137" class="el_method">getAllSessionsNotExpired()</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a6"><a href="SessionPersistentRegistry.java.html#L157" class="el_method">getMaxInactiveInterval()</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">1</td><td class="ctr2" id="i11">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a7"><a href="SessionPersistentRegistry.java.html#L162" class="el_method">getSessionEntity(String)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h12">1</td><td class="ctr2" id="i12">1</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a3"><a href="SessionPersistentRegistry.java.html#L142" class="el_method">getAllSessions()</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">1</td><td class="ctr2" id="i13">1</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry.java.html b/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry.java.html
    new file mode 100644
    index 000000000..2070aa7ea
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/SessionPersistentRegistry.java.html
    @@ -0,0 +1,193 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SessionPersistentRegistry.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_source">SessionPersistentRegistry.java</span></div><h1>SessionPersistentRegistry.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.session;
    +
    +import java.time.Duration;
    +import java.util.*;
    +
    +import org.springframework.beans.factory.annotation.Value;
    +import org.springframework.security.core.session.SessionInformation;
    +import org.springframework.security.core.session.SessionRegistry;
    +import org.springframework.security.core.userdetails.UserDetails;
    +import org.springframework.security.oauth2.core.user.OAuth2User;
    +import org.springframework.stereotype.Component;
    +
    +import jakarta.transaction.Transactional;
    +
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
    +import stirling.software.SPDF.model.SessionEntity;
    +
    +@Component
    +public class SessionPersistentRegistry implements SessionRegistry {
    +
    +    private final SessionRepository sessionRepository;
    +
    +    @Value(&quot;${server.servlet.session.timeout:30m}&quot;)
    +    private Duration defaultMaxInactiveInterval;
    +
    +<span class="nc" id="L26">    public SessionPersistentRegistry(SessionRepository sessionRepository) {</span>
    +<span class="nc" id="L27">        this.sessionRepository = sessionRepository;</span>
    +<span class="nc" id="L28">    }</span>
    +
    +    @Override
    +    public List&lt;Object&gt; getAllPrincipals() {
    +<span class="nc" id="L32">        List&lt;SessionEntity&gt; sessions = sessionRepository.findAll();</span>
    +<span class="nc" id="L33">        List&lt;Object&gt; principals = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L34" title="All 2 branches missed.">        for (SessionEntity session : sessions) {</span>
    +<span class="nc" id="L35">            principals.add(session.getPrincipalName());</span>
    +<span class="nc" id="L36">        }</span>
    +<span class="nc" id="L37">        return principals;</span>
    +    }
    +
    +    @Override
    +    public List&lt;SessionInformation&gt; getAllSessions(
    +            Object principal, boolean includeExpiredSessions) {
    +<span class="nc" id="L43">        List&lt;SessionInformation&gt; sessionInformations = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L44">        String principalName = null;</span>
    +
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">        if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L47">            principalName = detailsUser.getUsername();</span>
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">        } else if (principal instanceof OAuth2User oAuth2User) {</span>
    +<span class="nc" id="L49">            principalName = oAuth2User.getName();</span>
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">        } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {</span>
    +<span class="nc" id="L51">            principalName = saml2User.name();</span>
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">        } else if (principal instanceof String stringUser) {</span>
    +<span class="nc" id="L53">            principalName = stringUser;</span>
    +        }
    +
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">        if (principalName != null) {</span>
    +<span class="nc" id="L57">            List&lt;SessionEntity&gt; sessionEntities =</span>
    +<span class="nc" id="L58">                    sessionRepository.findByPrincipalName(principalName);</span>
    +<span class="nc bnc" id="L59" title="All 2 branches missed.">            for (SessionEntity sessionEntity : sessionEntities) {</span>
    +<span class="nc bnc" id="L60" title="All 4 branches missed.">                if (includeExpiredSessions || !sessionEntity.isExpired()) {</span>
    +<span class="nc" id="L61">                    sessionInformations.add(</span>
    +                            new SessionInformation(
    +<span class="nc" id="L63">                                    sessionEntity.getPrincipalName(),</span>
    +<span class="nc" id="L64">                                    sessionEntity.getSessionId(),</span>
    +<span class="nc" id="L65">                                    sessionEntity.getLastRequest()));</span>
    +                }
    +<span class="nc" id="L67">            }</span>
    +        }
    +<span class="nc" id="L69">        return sessionInformations;</span>
    +    }
    +
    +    @Override
    +    @Transactional
    +    public void registerNewSession(String sessionId, Object principal) {
    +<span class="nc" id="L75">        String principalName = null;</span>
    +
    +<span class="nc bnc" id="L77" title="All 2 branches missed.">        if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L78">            principalName = detailsUser.getUsername();</span>
    +<span class="nc bnc" id="L79" title="All 2 branches missed.">        } else if (principal instanceof OAuth2User oAuth2User) {</span>
    +<span class="nc" id="L80">            principalName = oAuth2User.getName();</span>
    +<span class="nc bnc" id="L81" title="All 2 branches missed.">        } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {</span>
    +<span class="nc" id="L82">            principalName = saml2User.name();</span>
    +<span class="nc bnc" id="L83" title="All 2 branches missed.">        } else if (principal instanceof String stringUser) {</span>
    +<span class="nc" id="L84">            principalName = stringUser;</span>
    +        }
    +
    +<span class="nc bnc" id="L87" title="All 2 branches missed.">        if (principalName != null) {</span>
    +            // Clear old sessions for the principal (unsure if needed)
    +            //            List&lt;SessionEntity&gt; existingSessions =
    +            //                    sessionRepository.findByPrincipalName(principalName);
    +            //            for (SessionEntity session : existingSessions) {
    +            //                session.setExpired(true);
    +            //                sessionRepository.save(session);
    +            //            }
    +
    +<span class="nc" id="L96">            SessionEntity sessionEntity = new SessionEntity();</span>
    +<span class="nc" id="L97">            sessionEntity.setSessionId(sessionId);</span>
    +<span class="nc" id="L98">            sessionEntity.setPrincipalName(principalName);</span>
    +<span class="nc" id="L99">            sessionEntity.setLastRequest(new Date()); // Set lastRequest to the current date</span>
    +<span class="nc" id="L100">            sessionEntity.setExpired(false);</span>
    +<span class="nc" id="L101">            sessionRepository.save(sessionEntity);</span>
    +        }
    +<span class="nc" id="L103">    }</span>
    +
    +    @Override
    +    @Transactional
    +    public void removeSessionInformation(String sessionId) {
    +<span class="nc" id="L108">        sessionRepository.deleteById(sessionId);</span>
    +<span class="nc" id="L109">    }</span>
    +
    +    @Override
    +    @Transactional
    +    public void refreshLastRequest(String sessionId) {
    +<span class="nc" id="L114">        Optional&lt;SessionEntity&gt; sessionEntityOpt = sessionRepository.findById(sessionId);</span>
    +<span class="nc bnc" id="L115" title="All 2 branches missed.">        if (sessionEntityOpt.isPresent()) {</span>
    +<span class="nc" id="L116">            SessionEntity sessionEntity = sessionEntityOpt.get();</span>
    +<span class="nc" id="L117">            sessionEntity.setLastRequest(new Date()); // Update lastRequest to the current date</span>
    +<span class="nc" id="L118">            sessionRepository.save(sessionEntity);</span>
    +        }
    +<span class="nc" id="L120">    }</span>
    +
    +    @Override
    +    public SessionInformation getSessionInformation(String sessionId) {
    +<span class="nc" id="L124">        Optional&lt;SessionEntity&gt; sessionEntityOpt = sessionRepository.findById(sessionId);</span>
    +<span class="nc bnc" id="L125" title="All 2 branches missed.">        if (sessionEntityOpt.isPresent()) {</span>
    +<span class="nc" id="L126">            SessionEntity sessionEntity = sessionEntityOpt.get();</span>
    +<span class="nc" id="L127">            return new SessionInformation(</span>
    +<span class="nc" id="L128">                    sessionEntity.getPrincipalName(),</span>
    +<span class="nc" id="L129">                    sessionEntity.getSessionId(),</span>
    +<span class="nc" id="L130">                    sessionEntity.getLastRequest());</span>
    +        }
    +<span class="nc" id="L132">        return null;</span>
    +    }
    +
    +    // Retrieve all non-expired sessions
    +    public List&lt;SessionEntity&gt; getAllSessionsNotExpired() {
    +<span class="nc" id="L137">        return sessionRepository.findByExpired(false);</span>
    +    }
    +
    +    // Retrieve all sessions
    +    public List&lt;SessionEntity&gt; getAllSessions() {
    +<span class="nc" id="L142">        return sessionRepository.findAll();</span>
    +    }
    +
    +    // Mark a session as expired
    +    public void expireSession(String sessionId) {
    +<span class="nc" id="L147">        Optional&lt;SessionEntity&gt; sessionEntityOpt = sessionRepository.findById(sessionId);</span>
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">        if (sessionEntityOpt.isPresent()) {</span>
    +<span class="nc" id="L149">            SessionEntity sessionEntity = sessionEntityOpt.get();</span>
    +<span class="nc" id="L150">            sessionEntity.setExpired(true); // Set expired to true</span>
    +<span class="nc" id="L151">            sessionRepository.save(sessionEntity);</span>
    +        }
    +<span class="nc" id="L153">    }</span>
    +
    +    // Get the maximum inactive interval for sessions
    +    public int getMaxInactiveInterval() {
    +<span class="nc" id="L157">        return (int) defaultMaxInactiveInterval.getSeconds();</span>
    +    }
    +
    +    // Retrieve a session entity by session ID
    +    public SessionEntity getSessionEntity(String sessionId) {
    +<span class="nc" id="L162">        return sessionRepository.findBySessionId(sessionId);</span>
    +    }
    +
    +    // Update session details by principal name
    +    public void updateSessionByPrincipalName(
    +            String principalName, boolean expired, Date lastRequest) {
    +<span class="nc" id="L168">        sessionRepository.saveByPrincipalName(expired, lastRequest, principalName);</span>
    +<span class="nc" id="L169">    }</span>
    +
    +    // Find the latest session for a given principal name
    +    public Optional&lt;SessionEntity&gt; findLatestSession(String principalName) {
    +<span class="nc" id="L173">        List&lt;SessionEntity&gt; allSessions = sessionRepository.findByPrincipalName(principalName);</span>
    +<span class="nc bnc" id="L174" title="All 2 branches missed.">        if (allSessions.isEmpty()) {</span>
    +<span class="nc" id="L175">            return Optional.empty();</span>
    +        }
    +
    +        // Sort sessions by lastRequest in descending order
    +<span class="nc" id="L179">        Collections.sort(</span>
    +                allSessions,
    +<span class="nc" id="L181">                new Comparator&lt;SessionEntity&gt;() {</span>
    +                    @Override
    +                    public int compare(SessionEntity s1, SessionEntity s2) {
    +                        // Sort by lastRequest in descending order
    +<span class="nc" id="L185">                        return s2.getLastRequest().compareTo(s1.getLastRequest());</span>
    +                    }
    +                });
    +
    +        // The first session in the list is the latest session for the given principal name
    +<span class="nc" id="L190">        return Optional.of(allSessions.get(0));</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/SessionRegistryConfig.html b/testResult/html/stirling.software.SPDF.config.security.session/SessionRegistryConfig.html
    new file mode 100644
    index 000000000..6df43251b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/SessionRegistryConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SessionRegistryConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_class">SessionRegistryConfig</span></div><h1>SessionRegistryConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="SessionRegistryConfig.java.html#L18" class="el_method">sessionPersistentRegistry(SessionRepository)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="5" alt="5"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="SessionRegistryConfig.java.html#L12" class="el_method">sessionRegistry()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="SessionRegistryConfig.java.html#L8" class="el_method">SessionRegistryConfig()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/SessionRegistryConfig.java.html b/testResult/html/stirling.software.SPDF.config.security.session/SessionRegistryConfig.java.html
    new file mode 100644
    index 000000000..6e869348e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/SessionRegistryConfig.java.html
    @@ -0,0 +1,21 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SessionRegistryConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_source">SessionRegistryConfig.java</span></div><h1>SessionRegistryConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.session;
    +
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.security.core.session.SessionRegistryImpl;
    +
    +@Configuration
    +<span class="nc" id="L8">public class SessionRegistryConfig {</span>
    +
    +    @Bean
    +    public SessionRegistryImpl sessionRegistry() {
    +<span class="nc" id="L12">        return new SessionRegistryImpl();</span>
    +    }
    +
    +    @Bean
    +    public SessionPersistentRegistry sessionPersistentRegistry(
    +            SessionRepository sessionRepository) {
    +<span class="nc" id="L18">        return new SessionPersistentRegistry(sessionRepository);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/SessionScheduled.html b/testResult/html/stirling.software.SPDF.config.security.session/SessionScheduled.html
    new file mode 100644
    index 000000000..d9d445c6c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/SessionScheduled.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SessionScheduled</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_class">SessionScheduled</span></div><h1>SessionScheduled</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">61 of 61</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">17</td><td class="ctr2">17</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="SessionScheduled.java.html#L23" class="el_method">expireSessions()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="55" alt="55"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">14</td><td class="ctr2" id="i0">14</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="SessionScheduled.java.html#L17" class="el_method">SessionScheduled(SessionPersistentRegistry)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/SessionScheduled.java.html b/testResult/html/stirling.software.SPDF.config.security.session/SessionScheduled.java.html
    new file mode 100644
    index 000000000..6f74a50bb
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/SessionScheduled.java.html
    @@ -0,0 +1,39 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SessionScheduled.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security.session</a> &gt; <span class="el_source">SessionScheduled.java</span></div><h1>SessionScheduled.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security.session;
    +
    +import java.time.Instant;
    +import java.time.temporal.ChronoUnit;
    +import java.util.Date;
    +import java.util.List;
    +
    +import org.springframework.scheduling.annotation.Scheduled;
    +import org.springframework.security.core.session.SessionInformation;
    +import org.springframework.stereotype.Component;
    +
    +@Component
    +public class SessionScheduled {
    +
    +    private final SessionPersistentRegistry sessionPersistentRegistry;
    +
    +<span class="nc" id="L17">    public SessionScheduled(SessionPersistentRegistry sessionPersistentRegistry) {</span>
    +<span class="nc" id="L18">        this.sessionPersistentRegistry = sessionPersistentRegistry;</span>
    +<span class="nc" id="L19">    }</span>
    +
    +    @Scheduled(cron = &quot;0 0/5 * * * ?&quot;)
    +    public void expireSessions() {
    +<span class="nc" id="L23">        Instant now = Instant.now();</span>
    +<span class="nc bnc" id="L24" title="All 2 branches missed.">        for (Object principal : sessionPersistentRegistry.getAllPrincipals()) {</span>
    +<span class="nc" id="L25">            List&lt;SessionInformation&gt; sessionInformations =</span>
    +<span class="nc" id="L26">                    sessionPersistentRegistry.getAllSessions(principal, false);</span>
    +<span class="nc bnc" id="L27" title="All 2 branches missed.">            for (SessionInformation sessionInformation : sessionInformations) {</span>
    +<span class="nc" id="L28">                Date lastRequest = sessionInformation.getLastRequest();</span>
    +<span class="nc" id="L29">                int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();</span>
    +<span class="nc" id="L30">                Instant expirationTime =</span>
    +<span class="nc" id="L31">                        lastRequest.toInstant().plus(maxInactiveInterval, ChronoUnit.SECONDS);</span>
    +<span class="nc bnc" id="L32" title="All 2 branches missed.">                if (now.isAfter(expirationTime)) {</span>
    +<span class="nc" id="L33">                    sessionPersistentRegistry.expireSession(sessionInformation.getSessionId());</span>
    +                }
    +<span class="nc" id="L35">            }</span>
    +<span class="nc" id="L36">        }</span>
    +<span class="nc" id="L37">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/index.html b/testResult/html/stirling.software.SPDF.config.security.session/index.html
    new file mode 100644
    index 000000000..b22dc8775
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security.session</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security.session</span></div><h1>stirling.software.SPDF.config.security.session</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">403 of 403</td><td class="ctr2">0%</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="ctr1">46</td><td class="ctr2">46</td><td class="ctr1">109</td><td class="ctr2">109</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a1"><a href="SessionPersistentRegistry.html" class="el_class">SessionPersistentRegistry</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="300" alt="300"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">32</td><td class="ctr2" id="g0">32</td><td class="ctr1" id="h0">80</td><td class="ctr2" id="i0">80</td><td class="ctr1" id="j0">14</td><td class="ctr2" id="k0">14</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a4"><a href="SessionScheduled.html" class="el_class">SessionScheduled</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="61" alt="61"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h1">17</td><td class="ctr2" id="i1">17</td><td class="ctr1" id="j3">2</td><td class="ctr2" id="k3">2</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a0"><a href="CustomHttpSessionListener.html" class="el_class">CustomHttpSessionListener</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="18" alt="18"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h2">7</td><td class="ctr2" id="i2">7</td><td class="ctr1" id="j1">4</td><td class="ctr2" id="k1">4</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="SessionRegistryConfig.html" class="el_class">SessionRegistryConfig</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="12" alt="12"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j2">3</td><td class="ctr2" id="k2">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a2"><a href="SessionPersistentRegistry$1.html" class="el_class">SessionPersistentRegistry.new Comparator() {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="12" alt="12"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j4">2</td><td class="ctr2" id="k4">2</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security.session/index.source.html b/testResult/html/stirling.software.SPDF.config.security.session/index.source.html
    new file mode 100644
    index 000000000..1347b1971
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security.session/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security.session</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security.session</span></div><h1>stirling.software.SPDF.config.security.session</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">403 of 403</td><td class="ctr2">0%</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="ctr1">46</td><td class="ctr2">46</td><td class="ctr1">109</td><td class="ctr2">109</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a1"><a href="SessionPersistentRegistry.java.html" class="el_source">SessionPersistentRegistry.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="312" alt="312"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="36" alt="36"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">34</td><td class="ctr2" id="g0">34</td><td class="ctr1" id="h0">82</td><td class="ctr2" id="i0">82</td><td class="ctr1" id="j0">16</td><td class="ctr2" id="k0">16</td><td class="ctr1" id="l0">2</td><td class="ctr2" id="m0">2</td></tr><tr><td id="a3"><a href="SessionScheduled.java.html" class="el_source">SessionScheduled.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="61" alt="61"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h1">17</td><td class="ctr2" id="i1">17</td><td class="ctr1" id="j3">2</td><td class="ctr2" id="k3">2</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a0"><a href="CustomHttpSessionListener.java.html" class="el_source">CustomHttpSessionListener.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="18" alt="18"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h2">7</td><td class="ctr2" id="i2">7</td><td class="ctr1" id="j1">4</td><td class="ctr2" id="k1">4</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a2"><a href="SessionRegistryConfig.java.html" class="el_source">SessionRegistryConfig.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="12" alt="12"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j2">3</td><td class="ctr2" id="k2">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/AppUpdateAuthService.html b/testResult/html/stirling.software.SPDF.config.security/AppUpdateAuthService.html
    new file mode 100644
    index 000000000..200e6a325
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/AppUpdateAuthService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AppUpdateAuthService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">AppUpdateAuthService</span></div><h1>AppUpdateAuthService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">69 of 69</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">17</td><td class="ctr2">17</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="AppUpdateAuthService.java.html#L29" class="el_method">getShowUpdateOnlyAdmins()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="60" alt="60"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">13</td><td class="ctr2" id="i0">13</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="AppUpdateAuthService.java.html#L22" class="el_method">AppUpdateAuthService(UserRepository, ApplicationProperties)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/AppUpdateAuthService.java.html b/testResult/html/stirling.software.SPDF.config.security/AppUpdateAuthService.java.html
    new file mode 100644
    index 000000000..535bf3540
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/AppUpdateAuthService.java.html
    @@ -0,0 +1,48 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AppUpdateAuthService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">AppUpdateAuthService.java</span></div><h1>AppUpdateAuthService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.util.Optional;
    +
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.core.context.SecurityContextHolder;
    +import org.springframework.stereotype.Service;
    +
    +import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.User;
    +import stirling.software.SPDF.repository.UserRepository;
    +
    +@Service
    +class AppUpdateAuthService implements ShowAdminInterface {
    +
    +    private final UserRepository userRepository;
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    public AppUpdateAuthService(
    +<span class="nc" id="L22">            UserRepository userRepository, ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L23">        this.userRepository = userRepository;</span>
    +<span class="nc" id="L24">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L25">    }</span>
    +
    +    @Override
    +    public boolean getShowUpdateOnlyAdmins() {
    +<span class="nc" id="L29">        boolean showUpdate = applicationProperties.getSystem().isShowUpdate();</span>
    +<span class="nc bnc" id="L30" title="All 2 branches missed.">        if (!showUpdate) {</span>
    +<span class="nc" id="L31">            return showUpdate;</span>
    +        }
    +<span class="nc" id="L33">        boolean showUpdateOnlyAdmin = applicationProperties.getSystem().getShowUpdateOnlyAdmin();</span>
    +<span class="nc" id="L34">        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span>
    +<span class="nc bnc" id="L35" title="All 4 branches missed.">        if (authentication == null || !authentication.isAuthenticated()) {</span>
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">            return !showUpdateOnlyAdmin;</span>
    +        }
    +<span class="nc bnc" id="L38" title="All 2 branches missed.">        if (authentication.getName().equalsIgnoreCase(&quot;anonymousUser&quot;)) {</span>
    +<span class="nc bnc" id="L39" title="All 2 branches missed.">            return !showUpdateOnlyAdmin;</span>
    +        }
    +<span class="nc" id="L41">        Optional&lt;User&gt; user = userRepository.findByUsername(authentication.getName());</span>
    +<span class="nc bnc" id="L42" title="All 4 branches missed.">        if (user.isPresent() &amp;&amp; showUpdateOnlyAdmin) {</span>
    +<span class="nc" id="L43">            return &quot;ROLE_ADMIN&quot;.equals(user.get().getRolesAsString());</span>
    +        }
    +<span class="nc" id="L45">        return showUpdate;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationFailureHandler.html b/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationFailureHandler.html
    new file mode 100644
    index 000000000..4cb8df866
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationFailureHandler.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomAuthenticationFailureHandler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">CustomAuthenticationFailureHandler</span></div><h1>CustomAuthenticationFailureHandler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">144 of 144</td><td class="ctr2">0%</td><td class="bar">26 of 26</td><td class="ctr2">0%</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">36</td><td class="ctr2">36</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a3"><a href="CustomAuthenticationFailureHandler.java.html#L42" class="el_method">onAuthenticationFailure(HttpServletRequest, HttpServletResponse, AuthenticationException)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="111" alt="111"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">28</td><td class="ctr2" id="i0">28</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="CustomAuthenticationFailureHandler.java.html#L86" class="el_method">isDemoUser(Optional)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="15" alt="15"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="CustomAuthenticationFailureHandler.java.html#L30" class="el_method">CustomAuthenticationFailureHandler(LoginAttemptService, UserService)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="9" alt="9"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="CustomAuthenticationFailureHandler.java.html#L88" class="el_method">lambda$isDemoUser$0(Authority)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="5" alt="5"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="CustomAuthenticationFailureHandler.java.html#L22" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationFailureHandler.java.html b/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationFailureHandler.java.html
    new file mode 100644
    index 000000000..0e31befcf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationFailureHandler.java.html
    @@ -0,0 +1,91 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomAuthenticationFailureHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">CustomAuthenticationFailureHandler.java</span></div><h1>CustomAuthenticationFailureHandler.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.io.IOException;
    +import java.util.Optional;
    +
    +import org.springframework.security.authentication.BadCredentialsException;
    +import org.springframework.security.authentication.DisabledException;
    +import org.springframework.security.authentication.InternalAuthenticationServiceException;
    +import org.springframework.security.authentication.LockedException;
    +import org.springframework.security.core.AuthenticationException;
    +import org.springframework.security.core.userdetails.UsernameNotFoundException;
    +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
    +
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.User;
    +
    +<span class="nc" id="L22">@Slf4j</span>
    +public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    +
    +    private LoginAttemptService loginAttemptService;
    +
    +    private UserService userService;
    +
    +    public CustomAuthenticationFailureHandler(
    +<span class="nc" id="L30">            final LoginAttemptService loginAttemptService, UserService userService) {</span>
    +<span class="nc" id="L31">        this.loginAttemptService = loginAttemptService;</span>
    +<span class="nc" id="L32">        this.userService = userService;</span>
    +<span class="nc" id="L33">    }</span>
    +
    +    @Override
    +    public void onAuthenticationFailure(
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            AuthenticationException exception)
    +            throws IOException, ServletException {
    +
    +<span class="nc bnc" id="L42" title="All 2 branches missed.">        if (exception instanceof DisabledException) {</span>
    +<span class="nc" id="L43">            log.error(&quot;User is deactivated: &quot;, exception);</span>
    +<span class="nc" id="L44">            getRedirectStrategy().sendRedirect(request, response, &quot;/logout?userIsDisabled=true&quot;);</span>
    +<span class="nc" id="L45">            return;</span>
    +        }
    +
    +<span class="nc" id="L48">        String ip = request.getRemoteAddr();</span>
    +<span class="nc" id="L49">        log.error(&quot;Failed login attempt from IP: {}&quot;, ip);</span>
    +
    +<span class="nc bnc" id="L51" title="All 2 branches missed.">        if (exception instanceof LockedException) {</span>
    +<span class="nc" id="L52">            getRedirectStrategy().sendRedirect(request, response, &quot;/login?error=locked&quot;);</span>
    +<span class="nc" id="L53">            return;</span>
    +        }
    +
    +<span class="nc" id="L56">        String username = request.getParameter(&quot;username&quot;);</span>
    +<span class="nc" id="L57">        Optional&lt;User&gt; optUser = userService.findByUsernameIgnoreCase(username);</span>
    +
    +<span class="nc bnc" id="L59" title="All 6 branches missed.">        if (username != null &amp;&amp; optUser.isPresent() &amp;&amp; !isDemoUser(optUser)) {</span>
    +<span class="nc" id="L60">            log.info(</span>
    +                    &quot;Remaining attempts for user {}: {}&quot;,
    +                    username,
    +<span class="nc" id="L63">                    loginAttemptService.getRemainingAttempts(username));</span>
    +<span class="nc" id="L64">            loginAttemptService.loginFailed(username);</span>
    +<span class="nc bnc" id="L65" title="All 4 branches missed.">            if (loginAttemptService.isBlocked(username) || exception instanceof LockedException) {</span>
    +<span class="nc" id="L66">                getRedirectStrategy().sendRedirect(request, response, &quot;/login?error=locked&quot;);</span>
    +<span class="nc" id="L67">                return;</span>
    +            }
    +        }
    +<span class="nc bnc" id="L70" title="All 4 branches missed.">        if (exception instanceof BadCredentialsException</span>
    +                || exception instanceof UsernameNotFoundException) {
    +<span class="nc" id="L72">            getRedirectStrategy().sendRedirect(request, response, &quot;/login?error=badCredentials&quot;);</span>
    +<span class="nc" id="L73">            return;</span>
    +        }
    +<span class="nc bnc" id="L75" title="All 2 branches missed.">        if (exception instanceof InternalAuthenticationServiceException</span>
    +<span class="nc bnc" id="L76" title="All 2 branches missed.">                || &quot;Password must not be null&quot;.equalsIgnoreCase(exception.getMessage())) {</span>
    +<span class="nc" id="L77">            getRedirectStrategy()</span>
    +<span class="nc" id="L78">                    .sendRedirect(request, response, &quot;/login?error=oauth2AuthenticationError&quot;);</span>
    +<span class="nc" id="L79">            return;</span>
    +        }
    +
    +<span class="nc" id="L82">        super.onAuthenticationFailure(request, response, exception);</span>
    +<span class="nc" id="L83">    }</span>
    +
    +    private boolean isDemoUser(Optional&lt;User&gt; user) {
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">        return user.isPresent()</span>
    +<span class="nc" id="L87">                &amp;&amp; user.get().getAuthorities().stream()</span>
    +<span class="nc bnc" id="L88" title="All 2 branches missed.">                        .anyMatch(authority -&gt; &quot;ROLE_DEMO_USER&quot;.equals(authority.getAuthority()));</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationSuccessHandler.html b/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationSuccessHandler.html
    new file mode 100644
    index 000000000..693e7f7e8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationSuccessHandler.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomAuthenticationSuccessHandler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">CustomAuthenticationSuccessHandler</span></div><h1>CustomAuthenticationSuccessHandler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">67 of 67</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">20</td><td class="ctr2">20</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="CustomAuthenticationSuccessHandler.java.html#L36" class="el_method">onAuthenticationSuccess(HttpServletRequest, HttpServletResponse, Authentication)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="54" alt="54"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">15</td><td class="ctr2" id="i0">15</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CustomAuthenticationSuccessHandler.java.html#L26" class="el_method">CustomAuthenticationSuccessHandler(LoginAttemptService, UserService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="CustomAuthenticationSuccessHandler.java.html#L18" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationSuccessHandler.java.html b/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationSuccessHandler.java.html
    new file mode 100644
    index 000000000..2f0606fc8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/CustomAuthenticationSuccessHandler.java.html
    @@ -0,0 +1,63 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomAuthenticationSuccessHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">CustomAuthenticationSuccessHandler.java</span></div><h1>CustomAuthenticationSuccessHandler.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.io.IOException;
    +
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
    +import org.springframework.security.web.savedrequest.SavedRequest;
    +
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +import jakarta.servlet.http.HttpSession;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.utils.RequestUriUtils;
    +
    +<span class="nc" id="L18">@Slf4j</span>
    +public class CustomAuthenticationSuccessHandler
    +        extends SavedRequestAwareAuthenticationSuccessHandler {
    +
    +    private LoginAttemptService loginAttemptService;
    +    private UserService userService;
    +
    +    public CustomAuthenticationSuccessHandler(
    +<span class="nc" id="L26">            LoginAttemptService loginAttemptService, UserService userService) {</span>
    +<span class="nc" id="L27">        this.loginAttemptService = loginAttemptService;</span>
    +<span class="nc" id="L28">        this.userService = userService;</span>
    +<span class="nc" id="L29">    }</span>
    +
    +    @Override
    +    public void onAuthenticationSuccess(
    +            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    +            throws ServletException, IOException {
    +
    +<span class="nc" id="L36">        String userName = request.getParameter(&quot;username&quot;);</span>
    +<span class="nc bnc" id="L37" title="All 2 branches missed.">        if (userService.isUserDisabled(userName)) {</span>
    +<span class="nc" id="L38">            getRedirectStrategy().sendRedirect(request, response, &quot;/logout?userIsDisabled=true&quot;);</span>
    +<span class="nc" id="L39">            return;</span>
    +        }
    +<span class="nc" id="L41">        loginAttemptService.loginSucceeded(userName);</span>
    +
    +        // Get the saved request
    +<span class="nc" id="L44">        HttpSession session = request.getSession(false);</span>
    +        SavedRequest savedRequest =
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">                (session != null)</span>
    +<span class="nc" id="L47">                        ? (SavedRequest) session.getAttribute(&quot;SPRING_SECURITY_SAVED_REQUEST&quot;)</span>
    +<span class="nc" id="L48">                        : null;</span>
    +
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">        if (savedRequest != null</span>
    +<span class="nc bnc" id="L51" title="All 2 branches missed.">                &amp;&amp; !RequestUriUtils.isStaticResource(</span>
    +<span class="nc" id="L52">                        request.getContextPath(), savedRequest.getRedirectUrl())) {</span>
    +            // Redirect to the original destination
    +<span class="nc" id="L54">            super.onAuthenticationSuccess(request, response, authentication);</span>
    +        } else {
    +            // Redirect to the root URL (considering context path)
    +<span class="nc" id="L57">            getRedirectStrategy().sendRedirect(request, response, &quot;/&quot;);</span>
    +        }
    +
    +        // super.onAuthenticationSuccess(request, response, authentication);
    +<span class="nc" id="L61">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/CustomLogoutSuccessHandler.html b/testResult/html/stirling.software.SPDF.config.security/CustomLogoutSuccessHandler.html
    new file mode 100644
    index 000000000..1a17c3fb1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/CustomLogoutSuccessHandler.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomLogoutSuccessHandler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">CustomLogoutSuccessHandler</span></div><h1>CustomLogoutSuccessHandler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">340 of 340</td><td class="ctr2">0%</td><td class="bar">39 of 39</td><td class="ctr2">0%</td><td class="ctr1">27</td><td class="ctr2">27</td><td class="ctr1">85</td><td class="ctr2">85</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a1"><a href="CustomLogoutSuccessHandler.java.html#L124" class="el_method">getRedirect_oauth2(HttpServletRequest, HttpServletResponse, OAuth2AuthenticationToken)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="102" alt="102"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="15" alt="15"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">27</td><td class="ctr2" id="i0">27</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="CustomLogoutSuccessHandler.java.html#L79" class="el_method">getRedirect_saml2(HttpServletRequest, HttpServletResponse, Saml2Authentication)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="89" height="10" title="76" alt="76"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h1">20</td><td class="ctr2" id="i1">20</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="CustomLogoutSuccessHandler.java.html#L46" class="el_method">onLogoutSuccess(HttpServletRequest, HttpServletResponse, Authentication)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="74" height="10" title="63" alt="63"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">6</td><td class="ctr2" id="g2">6</td><td class="ctr1" id="h3">14</td><td class="ctr2" id="i3">14</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="CustomLogoutSuccessHandler.java.html#L209" class="el_method">checkForErrors(HttpServletRequest)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="62" alt="62"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="112" height="10" title="14" alt="14"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">8</td><td class="ctr2" id="g1">8</td><td class="ctr1" id="h2">16</td><td class="ctr2" id="i2">16</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="CustomLogoutSuccessHandler.java.html#L179" class="el_method">getSamlClient(String, ApplicationProperties.Security.SAML2, List)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="28" alt="28"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">6</td><td class="ctr2" id="i4">6</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="CustomLogoutSuccessHandler.java.html#L237" class="el_method">sanitizeInput(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="5" alt="5"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="CustomLogoutSuccessHandler.java.html#L34" class="el_method">static {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/CustomLogoutSuccessHandler.java.html b/testResult/html/stirling.software.SPDF.config.security/CustomLogoutSuccessHandler.java.html
    new file mode 100644
    index 000000000..abdae507a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/CustomLogoutSuccessHandler.java.html
    @@ -0,0 +1,240 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomLogoutSuccessHandler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">CustomLogoutSuccessHandler.java</span></div><h1>CustomLogoutSuccessHandler.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.io.IOException;
    +import java.security.cert.X509Certificate;
    +import java.security.interfaces.RSAPrivateKey;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +import org.springframework.core.io.Resource;
    +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    +import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
    +import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
    +
    +import com.coveo.saml.SamlClient;
    +import com.coveo.saml.SamlException;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import lombok.AllArgsConstructor;
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.SPDFApplication;
    +import stirling.software.SPDF.config.security.saml2.CertificateUtils;
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
    +import stirling.software.SPDF.model.provider.KeycloakProvider;
    +import stirling.software.SPDF.utils.UrlUtils;
    +
    +<span class="nc" id="L34">@Slf4j</span>
    +@AllArgsConstructor
    +public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
    +
    +    public static final String LOGOUT_PATH = &quot;/login?logout=true&quot;;
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    @Override
    +    public void onLogoutSuccess(
    +            HttpServletRequest request, HttpServletResponse response, Authentication authentication)
    +            throws IOException {
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">        if (!response.isCommitted()) {</span>
    +<span class="nc bnc" id="L47" title="All 2 branches missed.">            if (authentication != null) {</span>
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">                if (authentication instanceof Saml2Authentication samlAuthentication) {</span>
    +                    // Handle SAML2 logout redirection
    +<span class="nc" id="L50">                    getRedirect_saml2(request, response, samlAuthentication);</span>
    +<span class="nc bnc" id="L51" title="All 2 branches missed.">                } else if (authentication instanceof OAuth2AuthenticationToken oAuthToken) {</span>
    +                    // Handle OAuth2 logout redirection
    +<span class="nc" id="L53">                    getRedirect_oauth2(request, response, oAuthToken);</span>
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">                } else if (authentication instanceof UsernamePasswordAuthenticationToken) {</span>
    +                    // Handle Username/Password logout
    +<span class="nc" id="L56">                    getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);</span>
    +                } else {
    +                    // Handle unknown authentication types
    +<span class="nc" id="L59">                    log.error(</span>
    +                            &quot;Authentication class unknown: {}&quot;,
    +<span class="nc" id="L61">                            authentication.getClass().getSimpleName());</span>
    +<span class="nc" id="L62">                    getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);</span>
    +                }
    +            } else {
    +                // Redirect to login page after logout
    +<span class="nc" id="L66">                String path = checkForErrors(request);</span>
    +<span class="nc" id="L67">                getRedirectStrategy().sendRedirect(request, response, path);</span>
    +            }
    +        }
    +<span class="nc" id="L70">    }</span>
    +
    +    // Redirect for SAML2 authentication logout
    +    private void getRedirect_saml2(
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            Saml2Authentication samlAuthentication)
    +            throws IOException {
    +
    +<span class="nc" id="L79">        SAML2 samlConf = applicationProperties.getSecurity().getSaml2();</span>
    +<span class="nc" id="L80">        String registrationId = samlConf.getRegistrationId();</span>
    +
    +<span class="nc" id="L82">        CustomSaml2AuthenticatedPrincipal principal =</span>
    +<span class="nc" id="L83">                (CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();</span>
    +
    +<span class="nc" id="L85">        String nameIdValue = principal.name();</span>
    +
    +        try {
    +            // Read certificate from the resource
    +<span class="nc" id="L89">            Resource certificateResource = samlConf.getSpCert();</span>
    +<span class="nc" id="L90">            X509Certificate certificate = CertificateUtils.readCertificate(certificateResource);</span>
    +
    +<span class="nc" id="L92">            List&lt;X509Certificate&gt; certificates = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L93">            certificates.add(certificate);</span>
    +
    +            // Construct URLs required for SAML configuration
    +<span class="nc" id="L96">            SamlClient samlClient = getSamlClient(registrationId, samlConf, certificates);</span>
    +
    +            // Read private key for service provider
    +<span class="nc" id="L99">            Resource privateKeyResource = samlConf.getPrivateKey();</span>
    +<span class="nc" id="L100">            RSAPrivateKey privateKey = CertificateUtils.readPrivateKey(privateKeyResource);</span>
    +
    +            // Set service provider keys for the SamlClient
    +<span class="nc" id="L103">            samlClient.setSPKeys(certificate, privateKey);</span>
    +
    +            // Redirect to identity provider for logout
    +<span class="nc" id="L106">            samlClient.redirectToIdentityProvider(response, null, nameIdValue);</span>
    +<span class="nc" id="L107">        } catch (Exception e) {</span>
    +<span class="nc" id="L108">            log.error(</span>
    +                    &quot;Error retrieving logout URL from Provider {} for user {}&quot;,
    +<span class="nc" id="L110">                    samlConf.getProvider(),</span>
    +                    nameIdValue,
    +                    e);
    +<span class="nc" id="L113">            getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);</span>
    +<span class="nc" id="L114">        }</span>
    +<span class="nc" id="L115">    }</span>
    +
    +    // Redirect for OAuth2 authentication logout
    +    private void getRedirect_oauth2(
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            OAuth2AuthenticationToken oAuthToken)
    +            throws IOException {
    +        String registrationId;
    +<span class="nc" id="L124">        OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();</span>
    +<span class="nc" id="L125">        String path = checkForErrors(request);</span>
    +
    +<span class="nc" id="L127">        String redirectUrl = UrlUtils.getOrigin(request) + &quot;/login?&quot; + path;</span>
    +<span class="nc" id="L128">        registrationId = oAuthToken.getAuthorizedClientRegistrationId();</span>
    +
    +        // Redirect based on OAuth2 provider
    +<span class="nc bnc" id="L131" title="All 3 branches missed.">        switch (registrationId.toLowerCase()) {</span>
    +            case &quot;keycloak&quot; -&gt; {
    +<span class="nc" id="L133">                KeycloakProvider keycloak = oauth.getClient().getKeycloak();</span>
    +
    +<span class="nc bnc" id="L135" title="All 2 branches missed.">                boolean isKeycloak = !keycloak.getIssuer().isBlank();</span>
    +<span class="nc bnc" id="L136" title="All 2 branches missed.">                boolean isCustomOAuth = !oauth.getIssuer().isBlank();</span>
    +
    +<span class="nc" id="L138">                String logoutUrl = redirectUrl;</span>
    +
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">                if (isKeycloak) {</span>
    +<span class="nc" id="L141">                    logoutUrl = keycloak.getIssuer();</span>
    +<span class="nc bnc" id="L142" title="All 2 branches missed.">                } else if (isCustomOAuth) {</span>
    +<span class="nc" id="L143">                    logoutUrl = oauth.getIssuer();</span>
    +                }
    +<span class="nc bnc" id="L145" title="All 4 branches missed.">                if (isKeycloak || isCustomOAuth) {</span>
    +<span class="nc" id="L146">                    logoutUrl +=</span>
    +                            &quot;/protocol/openid-connect/logout&quot;
    +                                    + &quot;?client_id=&quot;
    +<span class="nc" id="L149">                                    + oauth.getClientId()</span>
    +                                    + &quot;&amp;post_logout_redirect_uri=&quot;
    +<span class="nc" id="L151">                                    + response.encodeRedirectURL(redirectUrl);</span>
    +<span class="nc" id="L152">                    log.info(&quot;Redirecting to Keycloak logout URL: {}&quot;, logoutUrl);</span>
    +                } else {
    +<span class="nc" id="L154">                    log.info(</span>
    +                            &quot;No redirect URL for {} available. Redirecting to default logout URL: {}&quot;,
    +                            registrationId,
    +                            logoutUrl);
    +                }
    +<span class="nc" id="L159">                response.sendRedirect(logoutUrl);</span>
    +<span class="nc" id="L160">            }</span>
    +            case &quot;github&quot;, &quot;google&quot; -&gt; {
    +<span class="nc" id="L162">                log.info(</span>
    +                        &quot;No redirect URL for {} available. Redirecting to default logout URL: {}&quot;,
    +                        registrationId,
    +                        redirectUrl);
    +<span class="nc" id="L166">                response.sendRedirect(redirectUrl);</span>
    +<span class="nc" id="L167">            }</span>
    +            default -&gt; {
    +<span class="nc" id="L169">                log.info(&quot;Redirecting to default logout URL: {}&quot;, redirectUrl);</span>
    +<span class="nc" id="L170">                response.sendRedirect(redirectUrl);</span>
    +            }
    +        }
    +<span class="nc" id="L173">    }</span>
    +
    +    private static SamlClient getSamlClient(
    +            String registrationId, SAML2 samlConf, List&lt;X509Certificate&gt; certificates)
    +            throws SamlException {
    +        String serverUrl =
    +<span class="nc" id="L179">                SPDFApplication.getStaticBaseUrl() + &quot;:&quot; + SPDFApplication.getStaticPort();</span>
    +
    +<span class="nc" id="L181">        String relyingPartyIdentifier =</span>
    +                serverUrl + &quot;/saml2/service-provider-metadata/&quot; + registrationId;
    +
    +<span class="nc" id="L184">        String assertionConsumerServiceUrl = serverUrl + &quot;/login/saml2/sso/&quot; + registrationId;</span>
    +
    +<span class="nc" id="L186">        String idpSLOUrl = samlConf.getIdpSingleLogoutUrl();</span>
    +
    +<span class="nc" id="L188">        String idpIssuer = samlConf.getIdpIssuer();</span>
    +
    +        // Create SamlClient instance for SAML logout
    +<span class="nc" id="L191">        return new SamlClient(</span>
    +                relyingPartyIdentifier,
    +                assertionConsumerServiceUrl,
    +                idpSLOUrl,
    +                idpIssuer,
    +                certificates,
    +                SamlClient.SamlIdpBinding.POST);
    +    }
    +
    +    /**
    +     * Handles different error scenarios during logout. Will return a &lt;code&gt;String&lt;/code&gt; containing
    +     * the error request parameter.
    +     *
    +     * @param request the user's &lt;code&gt;HttpServletRequest&lt;/code&gt; request.
    +     * @return a &lt;code&gt;String&lt;/code&gt; containing the error request parameter.
    +     */
    +    private String checkForErrors(HttpServletRequest request) {
    +        String errorMessage;
    +<span class="nc" id="L209">        String path = &quot;logout=true&quot;;</span>
    +
    +<span class="nc bnc" id="L211" title="All 2 branches missed.">        if (request.getParameter(&quot;oAuth2AuthenticationErrorWeb&quot;) != null) {</span>
    +<span class="nc" id="L212">            path = &quot;errorOAuth=userAlreadyExistsWeb&quot;;</span>
    +<span class="nc bnc" id="L213" title="All 2 branches missed.">        } else if ((errorMessage = request.getParameter(&quot;errorOAuth&quot;)) != null) {</span>
    +<span class="nc" id="L214">            path = &quot;errorOAuth=&quot; + sanitizeInput(errorMessage);</span>
    +<span class="nc bnc" id="L215" title="All 2 branches missed.">        } else if (request.getParameter(&quot;oAuth2AutoCreateDisabled&quot;) != null) {</span>
    +<span class="nc" id="L216">            path = &quot;errorOAuth=oAuth2AutoCreateDisabled&quot;;</span>
    +<span class="nc bnc" id="L217" title="All 2 branches missed.">        } else if (request.getParameter(&quot;oAuth2AdminBlockedUser&quot;) != null) {</span>
    +<span class="nc" id="L218">            path = &quot;errorOAuth=oAuth2AdminBlockedUser&quot;;</span>
    +<span class="nc bnc" id="L219" title="All 2 branches missed.">        } else if (request.getParameter(&quot;userIsDisabled&quot;) != null) {</span>
    +<span class="nc" id="L220">            path = &quot;errorOAuth=userIsDisabled&quot;;</span>
    +<span class="nc bnc" id="L221" title="All 2 branches missed.">        } else if ((errorMessage = request.getParameter(&quot;error&quot;)) != null) {</span>
    +<span class="nc" id="L222">            path = &quot;errorOAuth=&quot; + sanitizeInput(errorMessage);</span>
    +<span class="nc bnc" id="L223" title="All 2 branches missed.">        } else if (request.getParameter(&quot;badCredentials&quot;) != null) {</span>
    +<span class="nc" id="L224">            path = &quot;errorOAuth=badCredentials&quot;;</span>
    +        }
    +
    +<span class="nc" id="L227">        return path;</span>
    +    }
    +
    +    /**
    +     * Sanitize input to avoid potential security vulnerabilities. Will return a sanitised &lt;code&gt;
    +     * String&lt;/code&gt;.
    +     *
    +     * @return a sanitised &lt;code&gt;String&lt;/code&gt;
    +     */
    +    private String sanitizeInput(String input) {
    +<span class="nc" id="L237">        return input.replaceAll(&quot;[^a-zA-Z0-9 ]&quot;, &quot;&quot;);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/CustomUserDetailsService.html b/testResult/html/stirling.software.SPDF.config.security/CustomUserDetailsService.html
    new file mode 100644
    index 000000000..42f8b6bfa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/CustomUserDetailsService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomUserDetailsService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">CustomUserDetailsService</span></div><h1>CustomUserDetailsService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">71 of 71</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">20</td><td class="ctr2">20</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a4"><a href="CustomUserDetailsService.java.html#L33" class="el_method">loadUserByUsername(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="44" alt="44"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CustomUserDetailsService.java.html#L26" class="el_method">CustomUserDetailsService(UserRepository, LoginAttemptService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="CustomUserDetailsService.java.html#L58" class="el_method">getAuthorities(Set)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="CustomUserDetailsService.java.html#L59" class="el_method">lambda$getAuthorities$1(Authority)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="CustomUserDetailsService.java.html#L38" class="el_method">lambda$loadUserByUsername$0(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/CustomUserDetailsService.java.html b/testResult/html/stirling.software.SPDF.config.security/CustomUserDetailsService.java.html
    new file mode 100644
    index 000000000..2c15a6715
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/CustomUserDetailsService.java.html
    @@ -0,0 +1,63 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomUserDetailsService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">CustomUserDetailsService.java</span></div><h1>CustomUserDetailsService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.util.Collection;
    +import java.util.Set;
    +
    +import org.springframework.security.authentication.LockedException;
    +import org.springframework.security.core.GrantedAuthority;
    +import org.springframework.security.core.authority.SimpleGrantedAuthority;
    +import org.springframework.security.core.userdetails.UserDetails;
    +import org.springframework.security.core.userdetails.UserDetailsService;
    +import org.springframework.security.core.userdetails.UsernameNotFoundException;
    +import org.springframework.stereotype.Service;
    +
    +import stirling.software.SPDF.model.Authority;
    +import stirling.software.SPDF.model.User;
    +import stirling.software.SPDF.repository.UserRepository;
    +
    +@Service
    +public class CustomUserDetailsService implements UserDetailsService {
    +
    +    private final UserRepository userRepository;
    +
    +    private final LoginAttemptService loginAttemptService;
    +
    +    public CustomUserDetailsService(
    +<span class="nc" id="L26">            UserRepository userRepository, LoginAttemptService loginAttemptService) {</span>
    +<span class="nc" id="L27">        this.userRepository = userRepository;</span>
    +<span class="nc" id="L28">        this.loginAttemptService = loginAttemptService;</span>
    +<span class="nc" id="L29">    }</span>
    +
    +    @Override
    +    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    +<span class="nc" id="L33">        User user =</span>
    +                userRepository
    +<span class="nc" id="L35">                        .findByUsername(username)</span>
    +<span class="nc" id="L36">                        .orElseThrow(</span>
    +                                () -&gt;
    +<span class="nc" id="L38">                                        new UsernameNotFoundException(</span>
    +                                                &quot;No user found with username: &quot; + username));
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">        if (loginAttemptService.isBlocked(username)) {</span>
    +<span class="nc" id="L41">            throw new LockedException(</span>
    +                    &quot;Your account has been locked due to too many failed login attempts.&quot;);
    +        }
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">        if (!user.hasPassword()) {</span>
    +<span class="nc" id="L45">            throw new IllegalArgumentException(&quot;Password must not be null&quot;);</span>
    +        }
    +<span class="nc" id="L47">        return new org.springframework.security.core.userdetails.User(</span>
    +<span class="nc" id="L48">                user.getUsername(),</span>
    +<span class="nc" id="L49">                user.getPassword(),</span>
    +<span class="nc" id="L50">                user.isEnabled(),</span>
    +                true,
    +                true,
    +                true,
    +<span class="nc" id="L54">                getAuthorities(user.getAuthorities()));</span>
    +    }
    +
    +    private Collection&lt;? extends GrantedAuthority&gt; getAuthorities(Set&lt;Authority&gt; authorities) {
    +<span class="nc" id="L58">        return authorities.stream()</span>
    +<span class="nc" id="L59">                .map(authority -&gt; new SimpleGrantedAuthority(authority.getAuthority()))</span>
    +<span class="nc" id="L60">                .toList();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/FirstLoginFilter.html b/testResult/html/stirling.software.SPDF.config.security/FirstLoginFilter.html
    new file mode 100644
    index 000000000..6cb6d0a15
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/FirstLoginFilter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FirstLoginFilter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">FirstLoginFilter</span></div><h1>FirstLoginFilter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">134 of 134</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">33</td><td class="ctr2">33</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="FirstLoginFilter.java.html#L39" class="el_method">doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="124" alt="124"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">29</td><td class="ctr2" id="i0">29</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="FirstLoginFilter.java.html#L31" class="el_method">FirstLoginFilter(UserService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="FirstLoginFilter.java.html#L25" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/FirstLoginFilter.java.html b/testResult/html/stirling.software.SPDF.config.security/FirstLoginFilter.java.html
    new file mode 100644
    index 000000000..50f447dee
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/FirstLoginFilter.java.html
    @@ -0,0 +1,77 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FirstLoginFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">FirstLoginFilter.java</span></div><h1>FirstLoginFilter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.io.IOException;
    +import java.text.SimpleDateFormat;
    +import java.util.Date;
    +import java.util.Optional;
    +
    +import org.springframework.context.annotation.Lazy;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.core.context.SecurityContextHolder;
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.filter.OncePerRequestFilter;
    +
    +import jakarta.servlet.FilterChain;
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +import jakarta.servlet.http.HttpSession;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.User;
    +import stirling.software.SPDF.utils.RequestUriUtils;
    +
    +<span class="nc" id="L25">@Slf4j</span>
    +@Component
    +public class FirstLoginFilter extends OncePerRequestFilter {
    +
    +    @Lazy private final UserService userService;
    +
    +<span class="nc" id="L31">    public FirstLoginFilter(@Lazy UserService userService) {</span>
    +<span class="nc" id="L32">        this.userService = userService;</span>
    +<span class="nc" id="L33">    }</span>
    +
    +    @Override
    +    protected void doFilterInternal(
    +            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    +            throws ServletException, IOException {
    +<span class="nc" id="L39">        String method = request.getMethod();</span>
    +<span class="nc" id="L40">        String requestURI = request.getRequestURI();</span>
    +<span class="nc" id="L41">        String contextPath = request.getContextPath();</span>
    +        // Check if the request is for static resources
    +<span class="nc" id="L43">        boolean isStaticResource = RequestUriUtils.isStaticResource(contextPath, requestURI);</span>
    +        // If it's a static resource, just continue the filter chain and skip the logic below
    +<span class="nc bnc" id="L45" title="All 2 branches missed.">        if (isStaticResource) {</span>
    +<span class="nc" id="L46">            filterChain.doFilter(request, response);</span>
    +<span class="nc" id="L47">            return;</span>
    +        }
    +<span class="nc" id="L49">        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span>
    +<span class="nc bnc" id="L50" title="All 4 branches missed.">        if (authentication != null &amp;&amp; authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L51">            Optional&lt;User&gt; user = userService.findByUsernameIgnoreCase(authentication.getName());</span>
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">            if (&quot;GET&quot;.equalsIgnoreCase(method)</span>
    +<span class="nc bnc" id="L53" title="All 2 branches missed.">                    &amp;&amp; user.isPresent()</span>
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">                    &amp;&amp; user.get().isFirstLogin()</span>
    +<span class="nc bnc" id="L55" title="All 2 branches missed.">                    &amp;&amp; !(contextPath + &quot;/change-creds&quot;).equals(requestURI)) {</span>
    +<span class="nc" id="L56">                response.sendRedirect(contextPath + &quot;/change-creds&quot;);</span>
    +<span class="nc" id="L57">                return;</span>
    +            }
    +        }
    +<span class="nc bnc" id="L60" title="All 2 branches missed.">        if (log.isDebugEnabled()) {</span>
    +<span class="nc" id="L61">            HttpSession session = request.getSession(true);</span>
    +<span class="nc" id="L62">            SimpleDateFormat timeFormat = new SimpleDateFormat(&quot;HH:mm:ss&quot;);</span>
    +<span class="nc" id="L63">            String creationTime = timeFormat.format(new Date(session.getCreationTime()));</span>
    +<span class="nc" id="L64">            log.debug(</span>
    +                    &quot;Request Info - New: {}, creationTimeSession {}, ID:  {}, IP: {}, User-Agent: {}, Referer: {}, Request URL: {}&quot;,
    +<span class="nc" id="L66">                    session.isNew(),</span>
    +                    creationTime,
    +<span class="nc" id="L68">                    session.getId(),</span>
    +<span class="nc" id="L69">                    request.getRemoteAddr(),</span>
    +<span class="nc" id="L70">                    request.getHeader(&quot;User-Agent&quot;),</span>
    +<span class="nc" id="L71">                    request.getHeader(&quot;Referer&quot;),</span>
    +<span class="nc" id="L72">                    request.getRequestURL().toString());</span>
    +        }
    +<span class="nc" id="L74">        filterChain.doFilter(request, response);</span>
    +<span class="nc" id="L75">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/IPRateLimitingFilter.html b/testResult/html/stirling.software.SPDF.config.security/IPRateLimitingFilter.html
    new file mode 100644
    index 000000000..048494588
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/IPRateLimitingFilter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>IPRateLimitingFilter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">IPRateLimitingFilter</span></div><h1>IPRateLimitingFilter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">103 of 103</td><td class="ctr2">0%</td><td class="bar">10 of 10</td><td class="ctr2">0%</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">29</td><td class="ctr2">29</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a0"><a href="IPRateLimitingFilter.java.html#L28" class="el_method">doFilter(ServletRequest, ServletResponse, FilterChain)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="72" alt="72"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">20</td><td class="ctr2" id="i0">20</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="IPRateLimitingFilter.java.html#L14" class="el_method">IPRateLimitingFilter(int, int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="19" alt="19"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="IPRateLimitingFilter.java.html#L63" class="el_method">resetRequestCounts()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="7" alt="7"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="IPRateLimitingFilter.java.html#L43" class="el_method">lambda$doFilter$0(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="5" alt="5"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/IPRateLimitingFilter.java.html b/testResult/html/stirling.software.SPDF.config.security/IPRateLimitingFilter.java.html
    new file mode 100644
    index 000000000..0ec287cf4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/IPRateLimitingFilter.java.html
    @@ -0,0 +1,67 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>IPRateLimitingFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">IPRateLimitingFilter.java</span></div><h1>IPRateLimitingFilter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.io.IOException;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.concurrent.atomic.AtomicInteger;
    +
    +import jakarta.servlet.*;
    +import jakarta.servlet.http.HttpServletRequest;
    +
    +import stirling.software.SPDF.utils.RequestUriUtils;
    +
    +public class IPRateLimitingFilter implements Filter {
    +
    +<span class="nc" id="L14">    private final ConcurrentHashMap&lt;String, AtomicInteger&gt; requestCounts =</span>
    +            new ConcurrentHashMap&lt;&gt;();
    +<span class="nc" id="L16">    private final ConcurrentHashMap&lt;String, AtomicInteger&gt; getCounts = new ConcurrentHashMap&lt;&gt;();</span>
    +    private final int maxRequests;
    +    private final int maxGetRequests;
    +
    +<span class="nc" id="L20">    public IPRateLimitingFilter(int maxRequests, int maxGetRequests) {</span>
    +<span class="nc" id="L21">        this.maxRequests = maxRequests;</span>
    +<span class="nc" id="L22">        this.maxGetRequests = maxGetRequests;</span>
    +<span class="nc" id="L23">    }</span>
    +
    +    @Override
    +    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    +            throws IOException, ServletException {
    +<span class="nc bnc" id="L28" title="All 2 branches missed.">        if (request instanceof HttpServletRequest httpServletRequest) {</span>
    +<span class="nc" id="L29">            HttpServletRequest httpRequest = httpServletRequest;</span>
    +<span class="nc" id="L30">            String method = httpRequest.getMethod();</span>
    +<span class="nc" id="L31">            String requestURI = httpRequest.getRequestURI();</span>
    +            // Check if the request is for static resources
    +<span class="nc" id="L33">            boolean isStaticResource =</span>
    +<span class="nc" id="L34">                    RequestUriUtils.isStaticResource(httpRequest.getContextPath(), requestURI);</span>
    +
    +            // If it's a static resource, just continue the filter chain and skip the logic below
    +<span class="nc bnc" id="L37" title="All 2 branches missed.">            if (isStaticResource) {</span>
    +<span class="nc" id="L38">                chain.doFilter(request, response);</span>
    +<span class="nc" id="L39">                return;</span>
    +            }
    +
    +<span class="nc" id="L42">            String clientIp = request.getRemoteAddr();</span>
    +<span class="nc" id="L43">            requestCounts.computeIfAbsent(clientIp, k -&gt; new AtomicInteger(0));</span>
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">            if (!&quot;GET&quot;.equalsIgnoreCase(method)) {</span>
    +
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">                if (requestCounts.get(clientIp).incrementAndGet() &gt; maxRequests) {</span>
    +                    // Handle limit exceeded (e.g., send error response)
    +<span class="nc" id="L48">                    response.getWriter().write(&quot;Rate limit exceeded&quot;);</span>
    +<span class="nc" id="L49">                    return;</span>
    +                }
    +            } else {
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">                if (requestCounts.get(clientIp).incrementAndGet() &gt; maxGetRequests) {</span>
    +                    // Handle limit exceeded (e.g., send error response)
    +<span class="nc" id="L54">                    response.getWriter().write(&quot;GET Rate limit exceeded&quot;);</span>
    +<span class="nc" id="L55">                    return;</span>
    +                }
    +            }
    +        }
    +<span class="nc" id="L59">        chain.doFilter(request, response);</span>
    +<span class="nc" id="L60">    }</span>
    +
    +    public void resetRequestCounts() {
    +<span class="nc" id="L63">        requestCounts.clear();</span>
    +<span class="nc" id="L64">        getCounts.clear();</span>
    +<span class="nc" id="L65">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/InitialSecuritySetup.html b/testResult/html/stirling.software.SPDF.config.security/InitialSecuritySetup.html
    new file mode 100644
    index 000000000..c6a2d8416
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/InitialSecuritySetup.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InitialSecuritySetup</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">InitialSecuritySetup</span></div><h1>InitialSecuritySetup</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">144 of 144</td><td class="ctr2">0%</td><td class="bar">18 of 18</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">44</td><td class="ctr2">44</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a2"><a href="InitialSecuritySetup.java.html#L57" class="el_method">initializeAdminUser()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="43" alt="43"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="InitialSecuritySetup.java.html#L86" class="el_method">initializeInternalApiUser()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="94" height="10" title="34" alt="34"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">9</td><td class="ctr2" id="i2">9</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="InitialSecuritySetup.java.html#L40" class="el_method">init()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="78" height="10" title="28" alt="28"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">11</td><td class="ctr2" id="i1">11</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="InitialSecuritySetup.java.html#L75" class="el_method">createDefaultAdminUser()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="23" alt="23"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">6</td><td class="ctr2" id="i3">6</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="InitialSecuritySetup.java.html#L30" class="el_method">InitialSecuritySetup(UserService, ApplicationProperties, DatabaseInterface)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="12" alt="12"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">5</td><td class="ctr2" id="i4">5</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="InitialSecuritySetup.java.html#L17" class="el_method">static {...}</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="4" alt="4"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/InitialSecuritySetup.java.html b/testResult/html/stirling.software.SPDF.config.security/InitialSecuritySetup.java.html
    new file mode 100644
    index 000000000..82385f95a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/InitialSecuritySetup.java.html
    @@ -0,0 +1,97 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InitialSecuritySetup.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">InitialSecuritySetup.java</span></div><h1>InitialSecuritySetup.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.sql.SQLException;
    +import java.util.UUID;
    +
    +import org.springframework.stereotype.Component;
    +
    +import jakarta.annotation.PostConstruct;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.interfaces.DatabaseInterface;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.Role;
    +import stirling.software.SPDF.model.exception.UnsupportedProviderException;
    +
    +<span class="nc" id="L17">@Slf4j</span>
    +@Component
    +public class InitialSecuritySetup {
    +
    +    private final UserService userService;
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    private final DatabaseInterface databaseService;
    +
    +    public InitialSecuritySetup(
    +            UserService userService,
    +            ApplicationProperties applicationProperties,
    +<span class="nc" id="L30">            DatabaseInterface databaseService) {</span>
    +<span class="nc" id="L31">        this.userService = userService;</span>
    +<span class="nc" id="L32">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L33">        this.databaseService = databaseService;</span>
    +<span class="nc" id="L34">    }</span>
    +
    +    @PostConstruct
    +    public void init() {
    +        try {
    +
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">            if (!userService.hasUsers()) {</span>
    +<span class="nc bnc" id="L41" title="All 2 branches missed.">                if (databaseService.hasBackup()) {</span>
    +<span class="nc" id="L42">                    databaseService.importDatabase();</span>
    +                } else {
    +<span class="nc" id="L44">                    initializeAdminUser();</span>
    +                }
    +            }
    +
    +<span class="nc" id="L48">            userService.migrateOauth2ToSSO();</span>
    +<span class="nc" id="L49">            initializeInternalApiUser();</span>
    +<span class="nc" id="L50">        } catch (IllegalArgumentException | SQLException | UnsupportedProviderException e) {</span>
    +<span class="nc" id="L51">            log.error(&quot;Failed to initialize security setup.&quot;, e);</span>
    +<span class="nc" id="L52">            System.exit(1);</span>
    +<span class="nc" id="L53">        }</span>
    +<span class="nc" id="L54">    }</span>
    +
    +    private void initializeAdminUser() throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L57">        String initialUsername =</span>
    +<span class="nc" id="L58">                applicationProperties.getSecurity().getInitialLogin().getUsername();</span>
    +<span class="nc" id="L59">        String initialPassword =</span>
    +<span class="nc" id="L60">                applicationProperties.getSecurity().getInitialLogin().getPassword();</span>
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        if (initialUsername != null</span>
    +<span class="nc bnc" id="L62" title="All 4 branches missed.">                &amp;&amp; !initialUsername.isEmpty()</span>
    +                &amp;&amp; initialPassword != null
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">                &amp;&amp; !initialPassword.isEmpty()</span>
    +<span class="nc bnc" id="L65" title="All 2 branches missed.">                &amp;&amp; userService.findByUsernameIgnoreCase(initialUsername).isEmpty()) {</span>
    +
    +<span class="nc" id="L67">            userService.saveUser(initialUsername, initialPassword, Role.ADMIN.getRoleId());</span>
    +<span class="nc" id="L68">            log.info(&quot;Admin user created: {}&quot;, initialUsername);</span>
    +        } else {
    +<span class="nc" id="L70">            createDefaultAdminUser();</span>
    +        }
    +<span class="nc" id="L72">    }</span>
    +
    +    private void createDefaultAdminUser() throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L75">        String defaultUsername = &quot;admin&quot;;</span>
    +<span class="nc" id="L76">        String defaultPassword = &quot;stirling&quot;;</span>
    +
    +<span class="nc bnc" id="L78" title="All 2 branches missed.">        if (userService.findByUsernameIgnoreCase(defaultUsername).isEmpty()) {</span>
    +<span class="nc" id="L79">            userService.saveUser(defaultUsername, defaultPassword, Role.ADMIN.getRoleId(), true);</span>
    +<span class="nc" id="L80">            log.info(&quot;Default admin user created: {}&quot;, defaultUsername);</span>
    +        }
    +<span class="nc" id="L82">    }</span>
    +
    +    private void initializeInternalApiUser()
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">        if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {</span>
    +<span class="nc" id="L87">            userService.saveUser(</span>
    +<span class="nc" id="L88">                    Role.INTERNAL_API_USER.getRoleId(),</span>
    +<span class="nc" id="L89">                    UUID.randomUUID().toString(),</span>
    +<span class="nc" id="L90">                    Role.INTERNAL_API_USER.getRoleId());</span>
    +<span class="nc" id="L91">            userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId());</span>
    +<span class="nc" id="L92">            log.info(&quot;Internal API user created: {}&quot;, Role.INTERNAL_API_USER.getRoleId());</span>
    +        }
    +<span class="nc" id="L94">        userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey());</span>
    +<span class="nc" id="L95">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/LoginAttemptService.html b/testResult/html/stirling.software.SPDF.config.security/LoginAttemptService.html
    new file mode 100644
    index 000000000..59e1771f0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/LoginAttemptService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LoginAttemptService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">LoginAttemptService</span></div><h1>LoginAttemptService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">161 of 161</td><td class="ctr2">0%</td><td class="bar">36 of 36</td><td class="ctr2">0%</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">40</td><td class="ctr2">40</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a4"><a href="LoginAttemptService.java.html#L54" class="el_method">loginFailed(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="41" alt="41"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">10</td><td class="ctr2" id="i0">10</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="LoginAttemptService.java.html#L70" class="el_method">isBlocked(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="31" alt="31"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="LoginAttemptService.java.html#L35" class="el_method">init()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="87" height="10" title="30" alt="30"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h1">9</td><td class="ctr2" id="i1">9</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="LoginAttemptService.java.html#L81" class="el_method">getRemainingAttempts(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="84" height="10" title="29" alt="29"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h3">6</td><td class="ctr2" id="i3">6</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="LoginAttemptService.java.html#L47" class="el_method">loginSucceeded(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="17" alt="17"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="6" alt="6"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">4</td><td class="ctr2" id="g3">4</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="LoginAttemptService.java.html#L27" class="el_method">LoginAttemptService(ApplicationProperties)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="9" alt="9"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">4</td><td class="ctr2" id="i5">4</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="LoginAttemptService.java.html#L16" class="el_method">static {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/LoginAttemptService.java.html b/testResult/html/stirling.software.SPDF.config.security/LoginAttemptService.java.html
    new file mode 100644
    index 000000000..62688e6a0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/LoginAttemptService.java.html
    @@ -0,0 +1,92 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LoginAttemptService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">LoginAttemptService.java</span></div><h1>LoginAttemptService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.concurrent.TimeUnit;
    +
    +import org.springframework.stereotype.Service;
    +
    +import jakarta.annotation.PostConstruct;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.AttemptCounter;
    +
    +@Service
    +<span class="nc" id="L16">@Slf4j</span>
    +public class LoginAttemptService {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    private int MAX_ATTEMPT;
    +
    +    private long ATTEMPT_INCREMENT_TIME;
    +
    +    private ConcurrentHashMap&lt;String, AttemptCounter&gt; attemptsCache;
    +
    +<span class="nc" id="L27">    private boolean isBlockedEnabled = true;</span>
    +
    +<span class="nc" id="L29">    public LoginAttemptService(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L30">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L31">    }</span>
    +
    +    @PostConstruct
    +    public void init() {
    +<span class="nc" id="L35">        MAX_ATTEMPT = applicationProperties.getSecurity().getLoginAttemptCount();</span>
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">        if (MAX_ATTEMPT == -1) {</span>
    +<span class="nc" id="L37">            isBlockedEnabled = false;</span>
    +<span class="nc" id="L38">            log.info(&quot;Login attempt tracking is disabled.&quot;);</span>
    +        }
    +<span class="nc" id="L40">        ATTEMPT_INCREMENT_TIME =</span>
    +<span class="nc" id="L41">                TimeUnit.MINUTES.toMillis(</span>
    +<span class="nc" id="L42">                        applicationProperties.getSecurity().getLoginResetTimeMinutes());</span>
    +<span class="nc" id="L43">        attemptsCache = new ConcurrentHashMap&lt;&gt;();</span>
    +<span class="nc" id="L44">    }</span>
    +
    +    public void loginSucceeded(String key) {
    +<span class="nc bnc" id="L47" title="All 6 branches missed.">        if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {</span>
    +<span class="nc" id="L48">            return;</span>
    +        }
    +<span class="nc" id="L50">        attemptsCache.remove(key.toLowerCase());</span>
    +<span class="nc" id="L51">    }</span>
    +
    +    public void loginFailed(String key) {
    +<span class="nc bnc" id="L54" title="All 6 branches missed.">        if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {</span>
    +<span class="nc" id="L55">            return;</span>
    +        }
    +<span class="nc" id="L57">        AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());</span>
    +<span class="nc bnc" id="L58" title="All 2 branches missed.">        if (attemptCounter == null) {</span>
    +<span class="nc" id="L59">            attemptCounter = new AttemptCounter();</span>
    +<span class="nc" id="L60">            attemptsCache.put(key.toLowerCase(), attemptCounter);</span>
    +        } else {
    +<span class="nc bnc" id="L62" title="All 2 branches missed.">            if (attemptCounter.shouldReset(ATTEMPT_INCREMENT_TIME)) {</span>
    +<span class="nc" id="L63">                attemptCounter.reset();</span>
    +            }
    +<span class="nc" id="L65">            attemptCounter.increment();</span>
    +        }
    +<span class="nc" id="L67">    }</span>
    +
    +    public boolean isBlocked(String key) {
    +<span class="nc bnc" id="L70" title="All 6 branches missed.">        if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {</span>
    +<span class="nc" id="L71">            return false;</span>
    +        }
    +<span class="nc" id="L73">        AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());</span>
    +<span class="nc bnc" id="L74" title="All 2 branches missed.">        if (attemptCounter == null) {</span>
    +<span class="nc" id="L75">            return false;</span>
    +        }
    +<span class="nc bnc" id="L77" title="All 2 branches missed.">        return attemptCounter.getAttemptCount() &gt;= MAX_ATTEMPT;</span>
    +    }
    +
    +    public int getRemainingAttempts(String key) {
    +<span class="nc bnc" id="L81" title="All 6 branches missed.">        if (!isBlockedEnabled || key == null || key.trim().isEmpty()) {</span>
    +            // Arbitrarily high number if tracking is disabled
    +<span class="nc" id="L83">            return Integer.MAX_VALUE;</span>
    +        }
    +<span class="nc" id="L85">        AttemptCounter attemptCounter = attemptsCache.get(key.toLowerCase());</span>
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">        if (attemptCounter == null) {</span>
    +<span class="nc" id="L87">            return MAX_ATTEMPT;</span>
    +        }
    +<span class="nc" id="L89">        return MAX_ATTEMPT - attemptCounter.getAttemptCount();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/RateLimitResetScheduler.html b/testResult/html/stirling.software.SPDF.config.security/RateLimitResetScheduler.html
    new file mode 100644
    index 000000000..aa262c2f1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/RateLimitResetScheduler.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RateLimitResetScheduler</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">RateLimitResetScheduler</span></div><h1>RateLimitResetScheduler</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">10 of 10</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="RateLimitResetScheduler.java.html#L11" class="el_method">RateLimitResetScheduler(IPRateLimitingFilter)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="RateLimitResetScheduler.java.html#L17" class="el_method">resetRateLimit()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/RateLimitResetScheduler.java.html b/testResult/html/stirling.software.SPDF.config.security/RateLimitResetScheduler.java.html
    new file mode 100644
    index 000000000..cca7e9eb3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/RateLimitResetScheduler.java.html
    @@ -0,0 +1,20 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RateLimitResetScheduler.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">RateLimitResetScheduler.java</span></div><h1>RateLimitResetScheduler.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import org.springframework.scheduling.annotation.Scheduled;
    +import org.springframework.stereotype.Component;
    +
    +@Component
    +public class RateLimitResetScheduler {
    +
    +    private final IPRateLimitingFilter rateLimitingFilter;
    +
    +<span class="nc" id="L11">    public RateLimitResetScheduler(IPRateLimitingFilter rateLimitingFilter) {</span>
    +<span class="nc" id="L12">        this.rateLimitingFilter = rateLimitingFilter;</span>
    +<span class="nc" id="L13">    }</span>
    +
    +    @Scheduled(cron = &quot;0 0 0 * * MON&quot;) // At 00:00 every Monday TODO: configurable
    +    public void resetRateLimit() {
    +<span class="nc" id="L17">        rateLimitingFilter.resetRequestCounts();</span>
    +<span class="nc" id="L18">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/SecurityConfiguration.html b/testResult/html/stirling.software.SPDF.config.security/SecurityConfiguration.html
    new file mode 100644
    index 000000000..d58bb17ff
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/SecurityConfiguration.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityConfiguration</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">SecurityConfiguration</span></div><h1>SecurityConfiguration</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">544 of 544</td><td class="ctr2">0%</td><td class="bar">48 of 48</td><td class="ctr2">0%</td><td class="ctr1">46</td><td class="ctr2">46</td><td class="ctr1">132</td><td class="ctr2">132</td><td class="ctr1">22</td><td class="ctr2">22</td></tr></tfoot><tbody><tr><td id="a2"><a href="SecurityConfiguration.java.html#L105" class="el_method">filterChain(HttpSecurity)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="143" alt="143"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">9</td><td class="ctr2" id="g1">9</td><td class="ctr1" id="h0">30</td><td class="ctr2" id="i0">30</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a14"><a href="SecurityConfiguration.java.html#L185" class="el_method">lambda$filterChain$7(HttpServletRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="73" alt="73"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="28" alt="28"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">15</td><td class="ctr2" id="g0">15</td><td class="ctr1" id="h1">19</td><td class="ctr2" id="i1">19</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a7"><a href="SecurityConfiguration.java.html#L269" class="el_method">lambda$filterChain$12(OpenSaml4AuthenticationProvider, Saml2LoginConfigurer)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="48" alt="48"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">11</td><td class="ctr2" id="i3">11</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a20"><a href="SecurityConfiguration.java.html#L82" class="el_method">SecurityConfiguration(PersistentLoginRepository, CustomUserDetailsService, UserService, boolean, boolean, ApplicationProperties, UserAuthenticationFilter, LoginAttemptService, FirstLoginFilter, SessionPersistentRegistry, GrantedAuthoritiesMapper, RelyingPartyRegistrationRepository, OpenSaml4AuthenticationRequestResolver)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="42" alt="42"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h2">15</td><td class="ctr2" id="i2">15</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a12"><a href="SecurityConfiguration.java.html#L160" class="el_method">lambda$filterChain$5(LogoutConfigurer)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="29" alt="29"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h7">5</td><td class="ctr2" id="i7">5</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a16"><a href="SecurityConfiguration.java.html#L217" class="el_method">lambda$filterChain$9(FormLoginConfigurer)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="27" alt="27"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h8">5</td><td class="ctr2" id="i8">5</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="SecurityConfiguration.java.html#L231" class="el_method">lambda$filterChain$11(OAuth2LoginConfigurer)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="25" alt="25"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h9">5</td><td class="ctr2" id="i9">5</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a4"><a href="SecurityConfiguration.java.html#L122" class="el_method">lambda$filterChain$1(HttpServletRequest)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="23" alt="23"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h4">8</td><td class="ctr2" id="i4">8</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a13"><a href="SecurityConfiguration.java.html#L168" class="el_method">lambda$filterChain$6(RememberMeConfigurer)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="19" alt="19"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h5">8</td><td class="ctr2" id="i5">8</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a5"><a href="SecurityConfiguration.java.html#L248" class="el_method">lambda$filterChain$10(OAuth2LoginConfigurer.UserInfoEndpointConfig)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="16" alt="16"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h12">3</td><td class="ctr2" id="i12">3</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a15"><a href="SecurityConfiguration.java.html#L183" class="el_method">lambda$filterChain$8(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="15" alt="15"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">4</td><td class="ctr2" id="i10">4</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a9"><a href="SecurityConfiguration.java.html#L120" class="el_method">lambda$filterChain$2(CookieCsrfTokenRepository, CsrfTokenRequestAttributeHandler, CsrfConfigurer)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="15" alt="15"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h13">3</td><td class="ctr2" id="i13">3</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a1"><a href="SecurityConfiguration.java.html#L297" class="el_method">daoAuthenticationProvider()</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="14" alt="14"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h11">4</td><td class="ctr2" id="i11">4</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a10"><a href="SecurityConfiguration.java.html#L150" class="el_method">lambda$filterChain$3(SessionManagementConfigurer)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="14" alt="14"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h6">6</td><td class="ctr2" id="i6">6</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a19"><a href="SecurityConfiguration.java.html#L306" class="el_method">rateLimitingFilter()</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="8" alt="8"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h14">2</td><td class="ctr2" id="i14">2</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a11"><a href="SecurityConfiguration.java.html#L157" class="el_method">lambda$filterChain$4(RequestCacheConfigurer)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h15">1</td><td class="ctr2" id="i15">1</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a18"><a href="SecurityConfiguration.java.html#L312" class="el_method">persistentTokenRepository()</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h16">1</td><td class="ctr2" id="i16">1</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a8"><a href="SecurityConfiguration.java.html#L291" class="el_method">lambda$filterChain$13(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h17">1</td><td class="ctr2" id="i17">1</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a17"><a href="SecurityConfiguration.java.html#L100" class="el_method">passwordEncoder()</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h18">1</td><td class="ctr2" id="i18">1</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a3"><a href="SecurityConfiguration.java.html#L106" class="el_method">lambda$filterChain$0(CsrfConfigurer)</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h19">1</td><td class="ctr2" id="i19">1</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a21"><a href="SecurityConfiguration.java.html#L48" class="el_method">static {...}</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">1</td><td class="ctr2" id="i20">1</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a0"><a href="SecurityConfiguration.java.html#L317" class="el_method">activeSecurity()</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">1</td><td class="ctr2" id="i21">1</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/SecurityConfiguration.java.html b/testResult/html/stirling.software.SPDF.config.security/SecurityConfiguration.java.html
    new file mode 100644
    index 000000000..3c86ca277
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/SecurityConfiguration.java.html
    @@ -0,0 +1,320 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityConfiguration.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">SecurityConfiguration.java</span></div><h1>SecurityConfiguration.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.util.Optional;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.context.annotation.DependsOn;
    +import org.springframework.context.annotation.Lazy;
    +import org.springframework.security.authentication.ProviderManager;
    +import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
    +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
    +import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    +import org.springframework.security.config.http.SessionCreationPolicy;
    +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
    +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    +import org.springframework.security.crypto.password.PasswordEncoder;
    +import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
    +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
    +import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver;
    +import org.springframework.security.web.SecurityFilterChain;
    +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    +import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
    +import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
    +import org.springframework.security.web.savedrequest.NullRequestCache;
    +import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
    +import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
    +import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
    +import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.User;
    +import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
    +import stirling.software.SPDF.repository.PersistentLoginRepository;
    +
    +@Configuration
    +@EnableWebSecurity
    +@EnableMethodSecurity
    +<span class="nc" id="L48">@Slf4j</span>
    +@DependsOn(&quot;runningProOrHigher&quot;)
    +public class SecurityConfiguration {
    +
    +    private final CustomUserDetailsService userDetailsService;
    +    private final UserService userService;
    +    private final boolean loginEnabledValue;
    +    private final boolean runningProOrHigher;
    +
    +    private final ApplicationProperties applicationProperties;
    +    private final UserAuthenticationFilter userAuthenticationFilter;
    +    private final LoginAttemptService loginAttemptService;
    +    private final FirstLoginFilter firstLoginFilter;
    +    private final SessionPersistentRegistry sessionRegistry;
    +    private final PersistentLoginRepository persistentLoginRepository;
    +    private final GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper;
    +    private final RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations;
    +    private final OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver;
    +
    +    public SecurityConfiguration(
    +            PersistentLoginRepository persistentLoginRepository,
    +            CustomUserDetailsService userDetailsService,
    +            @Lazy UserService userService,
    +            @Qualifier(&quot;loginEnabled&quot;) boolean loginEnabledValue,
    +            @Qualifier(&quot;runningProOrHigher&quot;) boolean runningProOrHigher,
    +            ApplicationProperties applicationProperties,
    +            UserAuthenticationFilter userAuthenticationFilter,
    +            LoginAttemptService loginAttemptService,
    +            FirstLoginFilter firstLoginFilter,
    +            SessionPersistentRegistry sessionRegistry,
    +            @Autowired(required = false) GrantedAuthoritiesMapper oAuth2userAuthoritiesMapper,
    +            @Autowired(required = false)
    +                    RelyingPartyRegistrationRepository saml2RelyingPartyRegistrations,
    +            @Autowired(required = false)
    +<span class="nc" id="L82">                    OpenSaml4AuthenticationRequestResolver saml2AuthenticationRequestResolver) {</span>
    +<span class="nc" id="L83">        this.userDetailsService = userDetailsService;</span>
    +<span class="nc" id="L84">        this.userService = userService;</span>
    +<span class="nc" id="L85">        this.loginEnabledValue = loginEnabledValue;</span>
    +<span class="nc" id="L86">        this.runningProOrHigher = runningProOrHigher;</span>
    +<span class="nc" id="L87">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L88">        this.userAuthenticationFilter = userAuthenticationFilter;</span>
    +<span class="nc" id="L89">        this.loginAttemptService = loginAttemptService;</span>
    +<span class="nc" id="L90">        this.firstLoginFilter = firstLoginFilter;</span>
    +<span class="nc" id="L91">        this.sessionRegistry = sessionRegistry;</span>
    +<span class="nc" id="L92">        this.persistentLoginRepository = persistentLoginRepository;</span>
    +<span class="nc" id="L93">        this.oAuth2userAuthoritiesMapper = oAuth2userAuthoritiesMapper;</span>
    +<span class="nc" id="L94">        this.saml2RelyingPartyRegistrations = saml2RelyingPartyRegistrations;</span>
    +<span class="nc" id="L95">        this.saml2AuthenticationRequestResolver = saml2AuthenticationRequestResolver;</span>
    +<span class="nc" id="L96">    }</span>
    +
    +    @Bean
    +    public PasswordEncoder passwordEncoder() {
    +<span class="nc" id="L100">        return new BCryptPasswordEncoder();</span>
    +    }
    +
    +    @Bean
    +    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    +<span class="nc bnc" id="L105" title="All 4 branches missed.">        if (applicationProperties.getSecurity().getCsrfDisabled() || !loginEnabledValue) {</span>
    +<span class="nc" id="L106">            http.csrf(csrf -&gt; csrf.disable());</span>
    +        }
    +
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">        if (loginEnabledValue) {</span>
    +<span class="nc" id="L110">            http.addFilterBefore(</span>
    +                    userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">            if (!applicationProperties.getSecurity().getCsrfDisabled()) {</span>
    +                CookieCsrfTokenRepository cookieRepo =
    +<span class="nc" id="L114">                        CookieCsrfTokenRepository.withHttpOnlyFalse();</span>
    +<span class="nc" id="L115">                CsrfTokenRequestAttributeHandler requestHandler =</span>
    +                        new CsrfTokenRequestAttributeHandler();
    +<span class="nc" id="L117">                requestHandler.setCsrfRequestAttributeName(null);</span>
    +<span class="nc" id="L118">                http.csrf(</span>
    +                        csrf -&gt;
    +<span class="nc" id="L120">                                csrf.ignoringRequestMatchers(</span>
    +                                                request -&gt; {
    +<span class="nc" id="L122">                                                    String apiKey = request.getHeader(&quot;X-API-KEY&quot;);</span>
    +                                                    // If there's no API key, don't ignore CSRF
    +                                                    // (return false)
    +<span class="nc bnc" id="L125" title="All 4 branches missed.">                                                    if (apiKey == null || apiKey.trim().isEmpty()) {</span>
    +<span class="nc" id="L126">                                                        return false;</span>
    +                                                    }
    +                                                    // Validate API key using existing UserService
    +                                                    try {
    +<span class="nc" id="L130">                                                        Optional&lt;User&gt; user =</span>
    +<span class="nc" id="L131">                                                                userService.getUserByApiKey(apiKey);</span>
    +                                                        // If API key is valid, ignore CSRF (return
    +                                                        // true)
    +                                                        // If API key is invalid, don't ignore CSRF
    +                                                        // (return false)
    +<span class="nc" id="L136">                                                        return user.isPresent();</span>
    +<span class="nc" id="L137">                                                    } catch (Exception e) {</span>
    +                                                        // If there's any error validating the API
    +                                                        // key, don't ignore CSRF
    +<span class="nc" id="L140">                                                        return false;</span>
    +                                                    }
    +                                                })
    +<span class="nc" id="L143">                                        .csrfTokenRepository(cookieRepo)</span>
    +<span class="nc" id="L144">                                        .csrfTokenRequestHandler(requestHandler));</span>
    +            }
    +<span class="nc" id="L146">            http.addFilterBefore(rateLimitingFilter(), UsernamePasswordAuthenticationFilter.class);</span>
    +<span class="nc" id="L147">            http.addFilterAfter(firstLoginFilter, UsernamePasswordAuthenticationFilter.class);</span>
    +<span class="nc" id="L148">            http.sessionManagement(</span>
    +                    sessionManagement -&gt;
    +<span class="nc" id="L150">                            sessionManagement</span>
    +<span class="nc" id="L151">                                    .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)</span>
    +<span class="nc" id="L152">                                    .maximumSessions(10)</span>
    +<span class="nc" id="L153">                                    .maxSessionsPreventsLogin(false)</span>
    +<span class="nc" id="L154">                                    .sessionRegistry(sessionRegistry)</span>
    +<span class="nc" id="L155">                                    .expiredUrl(&quot;/login?logout=true&quot;));</span>
    +<span class="nc" id="L156">            http.authenticationProvider(daoAuthenticationProvider());</span>
    +<span class="nc" id="L157">            http.requestCache(requestCache -&gt; requestCache.requestCache(new NullRequestCache()));</span>
    +<span class="nc" id="L158">            http.logout(</span>
    +                    logout -&gt;
    +<span class="nc" id="L160">                            logout.logoutRequestMatcher(new AntPathRequestMatcher(&quot;/logout&quot;))</span>
    +<span class="nc" id="L161">                                    .logoutSuccessHandler(</span>
    +                                            new CustomLogoutSuccessHandler(applicationProperties))
    +<span class="nc" id="L163">                                    .clearAuthentication(true)</span>
    +<span class="nc" id="L164">                                    .invalidateHttpSession(true)</span>
    +<span class="nc" id="L165">                                    .deleteCookies(&quot;JSESSIONID&quot;, &quot;remember-me&quot;));</span>
    +<span class="nc" id="L166">            http.rememberMe(</span>
    +                    rememberMeConfigurer -&gt; // Use the configurator directly
    +<span class="nc" id="L168">                    rememberMeConfigurer</span>
    +<span class="nc" id="L169">                                    .tokenRepository(persistentTokenRepository())</span>
    +<span class="nc" id="L170">                                    .tokenValiditySeconds( // 14 days</span>
    +                                            14 * 24 * 60 * 60)
    +<span class="nc" id="L172">                                    .userDetailsService( // Your existing UserDetailsService</span>
    +                                            userDetailsService)
    +<span class="nc" id="L174">                                    .useSecureCookie( // Enable secure cookie</span>
    +                                            true)
    +<span class="nc" id="L176">                                    .rememberMeParameter( // Form parameter name</span>
    +                                            &quot;remember-me&quot;)
    +<span class="nc" id="L178">                                    .rememberMeCookieName( // Cookie name</span>
    +                                            &quot;remember-me&quot;)
    +<span class="nc" id="L180">                                    .alwaysRemember(false));</span>
    +<span class="nc" id="L181">            http.authorizeHttpRequests(</span>
    +                    authz -&gt;
    +<span class="nc" id="L183">                            authz.requestMatchers(</span>
    +                                            req -&gt; {
    +<span class="nc" id="L185">                                                String uri = req.getRequestURI();</span>
    +<span class="nc" id="L186">                                                String contextPath = req.getContextPath();</span>
    +                                                // Remove the context path from the URI
    +                                                String trimmedUri =
    +<span class="nc bnc" id="L189" title="All 2 branches missed.">                                                        uri.startsWith(contextPath)</span>
    +<span class="nc" id="L190">                                                                ? uri.substring(</span>
    +<span class="nc" id="L191">                                                                        contextPath.length())</span>
    +<span class="nc" id="L192">                                                                : uri;</span>
    +<span class="nc bnc" id="L193" title="All 2 branches missed.">                                                return trimmedUri.startsWith(&quot;/login&quot;)</span>
    +<span class="nc bnc" id="L194" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/oauth&quot;)</span>
    +<span class="nc bnc" id="L195" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/saml2&quot;)</span>
    +<span class="nc bnc" id="L196" title="All 2 branches missed.">                                                        || trimmedUri.endsWith(&quot;.svg&quot;)</span>
    +<span class="nc bnc" id="L197" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/register&quot;)</span>
    +<span class="nc bnc" id="L198" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/error&quot;)</span>
    +<span class="nc bnc" id="L199" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/images/&quot;)</span>
    +<span class="nc bnc" id="L200" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/public/&quot;)</span>
    +<span class="nc bnc" id="L201" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/css/&quot;)</span>
    +<span class="nc bnc" id="L202" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/fonts/&quot;)</span>
    +<span class="nc bnc" id="L203" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(&quot;/js/&quot;)</span>
    +<span class="nc bnc" id="L204" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(</span>
    +                                                                &quot;/api/v1/info/status&quot;)
    +<span class="nc bnc" id="L206" title="All 2 branches missed.">                                                        || trimmedUri.startsWith(</span>
    +                                                                &quot;/api/v1/convert/&quot;);
    +                                            })
    +<span class="nc" id="L209">                                    .permitAll()</span>
    +<span class="nc" id="L210">                                    .anyRequest()</span>
    +<span class="nc" id="L211">                                    .authenticated());</span>
    +            // Handle User/Password Logins
    +<span class="nc bnc" id="L213" title="All 2 branches missed.">            if (applicationProperties.getSecurity().isUserPass()) {</span>
    +<span class="nc" id="L214">                http.formLogin(</span>
    +                        formLogin -&gt;
    +                                formLogin
    +<span class="nc" id="L217">                                        .loginPage(&quot;/login&quot;)</span>
    +<span class="nc" id="L218">                                        .successHandler(</span>
    +                                                new CustomAuthenticationSuccessHandler(
    +                                                        loginAttemptService, userService))
    +<span class="nc" id="L221">                                        .failureHandler(</span>
    +                                                new CustomAuthenticationFailureHandler(
    +                                                        loginAttemptService, userService))
    +<span class="nc" id="L224">                                        .defaultSuccessUrl(&quot;/&quot;)</span>
    +<span class="nc" id="L225">                                        .permitAll());</span>
    +            }
    +            // Handle OAUTH2 Logins
    +<span class="nc bnc" id="L228" title="All 2 branches missed.">            if (applicationProperties.getSecurity().isOauth2Active()) {</span>
    +<span class="nc" id="L229">                http.oauth2Login(</span>
    +                        oauth2 -&gt;
    +<span class="nc" id="L231">                                oauth2.loginPage(&quot;/oauth2&quot;)</span>
    +                                        .
    +                                        /*
    +                                        This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
    +                                        If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser'
    +                                        is set as true, else login fails with an error message advising the same.
    +                                         */
    +<span class="nc" id="L238">                                        successHandler(</span>
    +                                                new CustomOAuth2AuthenticationSuccessHandler(
    +                                                        loginAttemptService,
    +                                                        applicationProperties,
    +                                                        userService))
    +<span class="nc" id="L243">                                        .failureHandler(</span>
    +                                                new CustomOAuth2AuthenticationFailureHandler())
    +                                        . // Add existing Authorities from the database
    +<span class="nc" id="L246">                                        userInfoEndpoint(</span>
    +                                                userInfoEndpoint -&gt;
    +<span class="nc" id="L248">                                                        userInfoEndpoint</span>
    +<span class="nc" id="L249">                                                                .oidcUserService(</span>
    +                                                                        new CustomOAuth2UserService(
    +                                                                                applicationProperties,
    +                                                                                userService,
    +                                                                                loginAttemptService))
    +<span class="nc" id="L254">                                                                .userAuthoritiesMapper(</span>
    +                                                                        oAuth2userAuthoritiesMapper))
    +<span class="nc" id="L256">                                        .permitAll());</span>
    +            }
    +            // Handle SAML
    +<span class="nc bnc" id="L259" title="All 4 branches missed.">            if (applicationProperties.getSecurity().isSaml2Active() &amp;&amp; runningProOrHigher) {</span>
    +                // Configure the authentication provider
    +<span class="nc" id="L261">                OpenSaml4AuthenticationProvider authenticationProvider =</span>
    +                        new OpenSaml4AuthenticationProvider();
    +<span class="nc" id="L263">                authenticationProvider.setResponseAuthenticationConverter(</span>
    +                        new CustomSaml2ResponseAuthenticationConverter(userService));
    +<span class="nc" id="L265">                http.authenticationProvider(authenticationProvider)</span>
    +<span class="nc" id="L266">                        .saml2Login(</span>
    +                                saml2 -&gt; {
    +                                    try {
    +<span class="nc" id="L269">                                        saml2.loginPage(&quot;/saml2&quot;)</span>
    +<span class="nc" id="L270">                                                .relyingPartyRegistrationRepository(</span>
    +                                                        saml2RelyingPartyRegistrations)
    +<span class="nc" id="L272">                                                .authenticationManager(</span>
    +                                                        new ProviderManager(authenticationProvider))
    +<span class="nc" id="L274">                                                .successHandler(</span>
    +                                                        new CustomSaml2AuthenticationSuccessHandler(
    +                                                                loginAttemptService,
    +                                                                applicationProperties,
    +                                                                userService))
    +<span class="nc" id="L279">                                                .failureHandler(</span>
    +                                                        new CustomSaml2AuthenticationFailureHandler())
    +<span class="nc" id="L281">                                                .authenticationRequestResolver(</span>
    +                                                        saml2AuthenticationRequestResolver);
    +<span class="nc" id="L283">                                    } catch (Exception e) {</span>
    +<span class="nc" id="L284">                                        log.error(&quot;Error configuring SAML 2 login&quot;, e);</span>
    +<span class="nc" id="L285">                                        throw new RuntimeException(e);</span>
    +<span class="nc" id="L286">                                    }</span>
    +<span class="nc" id="L287">                                });</span>
    +<span class="nc" id="L288">            }</span>
    +        } else {
    +<span class="nc" id="L290">            log.debug(&quot;SAML 2 login is not enabled. Using default.&quot;);</span>
    +<span class="nc" id="L291">            http.authorizeHttpRequests(authz -&gt; authz.anyRequest().permitAll());</span>
    +        }
    +<span class="nc" id="L293">        return http.build();</span>
    +    }
    +
    +    public DaoAuthenticationProvider daoAuthenticationProvider() {
    +<span class="nc" id="L297">        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();</span>
    +<span class="nc" id="L298">        provider.setUserDetailsService(userDetailsService);</span>
    +<span class="nc" id="L299">        provider.setPasswordEncoder(passwordEncoder());</span>
    +<span class="nc" id="L300">        return provider;</span>
    +    }
    +
    +    @Bean
    +    public IPRateLimitingFilter rateLimitingFilter() {
    +        // Example limit TODO add config level
    +<span class="nc" id="L306">        int maxRequestsPerIp = 1000000;</span>
    +<span class="nc" id="L307">        return new IPRateLimitingFilter(maxRequestsPerIp, maxRequestsPerIp);</span>
    +    }
    +
    +    @Bean
    +    public PersistentTokenRepository persistentTokenRepository() {
    +<span class="nc" id="L312">        return new JPATokenRepositoryImpl(persistentLoginRepository);</span>
    +    }
    +
    +    @Bean
    +    public boolean activeSecurity() {
    +<span class="nc" id="L317">        return true;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter$LoginMethod.html b/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter$LoginMethod.html
    new file mode 100644
    index 000000000..95d3e944c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter$LoginMethod.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserAuthenticationFilter.LoginMethod</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">UserAuthenticationFilter.LoginMethod</span></div><h1>UserAuthenticationFilter.LoginMethod</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">49 of 49</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="UserAuthenticationFilter.java.html#L210" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="38" alt="38"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="UserAuthenticationFilter.java.html#L219" class="el_method">UserAuthenticationFilter.LoginMethod(String, int, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="UserAuthenticationFilter.java.html#L225" class="el_method">toString()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter.html b/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter.html
    new file mode 100644
    index 000000000..474faf2ca
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserAuthenticationFilter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">UserAuthenticationFilter</span></div><h1>UserAuthenticationFilter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">428 of 428</td><td class="ctr2">0%</td><td class="bar">72 of 72</td><td class="ctr2">0%</td><td class="ctr1">41</td><td class="ctr2">41</td><td class="ctr1">103</td><td class="ctr2">103</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="UserAuthenticationFilter.java.html#L61" class="el_method">doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="298" alt="298"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="62" alt="62"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">32</td><td class="ctr2" id="g0">32</td><td class="ctr1" id="h0">84</td><td class="ctr2" id="i0">84</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="UserAuthenticationFilter.java.html#L231" class="el_method">shouldNotFilter(HttpServletRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="105" alt="105"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h1">10</td><td class="ctr2" id="i1">10</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="UserAuthenticationFilter.java.html#L49" class="el_method">UserAuthenticationFilter(ApplicationProperties, UserService, SessionPersistentRegistry, boolean)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="15" alt="15"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="UserAuthenticationFilter.java.html#L99" class="el_method">lambda$doFilterInternal$0(Authority)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="UserAuthenticationFilter.java.html#L36" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter.java.html b/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter.java.html
    new file mode 100644
    index 000000000..b6c92bc8c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/UserAuthenticationFilter.java.html
    @@ -0,0 +1,260 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserAuthenticationFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">UserAuthenticationFilter.java</span></div><h1>UserAuthenticationFilter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.io.IOException;
    +import java.util.List;
    +import java.util.Optional;
    +
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.context.annotation.Lazy;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.core.AuthenticationException;
    +import org.springframework.security.core.authority.SimpleGrantedAuthority;
    +import org.springframework.security.core.context.SecurityContextHolder;
    +import org.springframework.security.core.session.SessionInformation;
    +import org.springframework.security.core.userdetails.UserDetails;
    +import org.springframework.security.oauth2.core.user.OAuth2User;
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.filter.OncePerRequestFilter;
    +
    +import jakarta.servlet.FilterChain;
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
    +import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
    +import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.Security;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
    +import stirling.software.SPDF.model.User;
    +
    +<span class="nc" id="L36">@Slf4j</span>
    +@Component
    +public class UserAuthenticationFilter extends OncePerRequestFilter {
    +
    +    private final ApplicationProperties applicationProperties;
    +    private final UserService userService;
    +    private final SessionPersistentRegistry sessionPersistentRegistry;
    +    private final boolean loginEnabledValue;
    +
    +    public UserAuthenticationFilter(
    +            @Lazy ApplicationProperties applicationProperties,
    +            @Lazy UserService userService,
    +            SessionPersistentRegistry sessionPersistentRegistry,
    +<span class="nc" id="L49">            @Qualifier(&quot;loginEnabled&quot;) boolean loginEnabledValue) {</span>
    +<span class="nc" id="L50">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L51">        this.userService = userService;</span>
    +<span class="nc" id="L52">        this.sessionPersistentRegistry = sessionPersistentRegistry;</span>
    +<span class="nc" id="L53">        this.loginEnabledValue = loginEnabledValue;</span>
    +<span class="nc" id="L54">    }</span>
    +
    +    @Override
    +    protected void doFilterInternal(
    +            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    +            throws ServletException, IOException {
    +
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        if (!loginEnabledValue) {</span>
    +            // If login is not enabled, just pass all requests without authentication
    +<span class="nc" id="L63">            filterChain.doFilter(request, response);</span>
    +<span class="nc" id="L64">            return;</span>
    +        }
    +<span class="nc" id="L66">        String requestURI = request.getRequestURI();</span>
    +<span class="nc" id="L67">        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span>
    +
    +        // Check for session expiration (unsure if needed)
    +        //        if (authentication != null &amp;&amp; authentication.isAuthenticated()) {
    +        //            String sessionId = request.getSession().getId();
    +        //            SessionInformation sessionInfo =
    +        //                    sessionPersistentRegistry.getSessionInformation(sessionId);
    +        //
    +        //            if (sessionInfo != null &amp;&amp; sessionInfo.isExpired()) {
    +        //                SecurityContextHolder.clearContext();
    +        //                response.sendRedirect(request.getContextPath() + &quot;/login?expired=true&quot;);
    +        //                return;
    +        //            }
    +        //        }
    +
    +        // Check for API key in the request headers if no authentication exists
    +<span class="nc bnc" id="L83" title="All 4 branches missed.">        if (authentication == null || !authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L84">            String apiKey = request.getHeader(&quot;X-API-KEY&quot;);</span>
    +<span class="nc bnc" id="L85" title="All 4 branches missed.">            if (apiKey != null &amp;&amp; !apiKey.trim().isEmpty()) {</span>
    +                try {
    +                    // Use API key to authenticate. This requires you to have an authentication
    +                    // provider for API keys.
    +<span class="nc" id="L89">                    Optional&lt;User&gt; user = userService.getUserByApiKey(apiKey);</span>
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">                    if (user.isEmpty()) {</span>
    +<span class="nc" id="L91">                        response.setStatus(HttpStatus.UNAUTHORIZED.value());</span>
    +<span class="nc" id="L92">                        response.getWriter().write(&quot;Invalid API Key.&quot;);</span>
    +<span class="nc" id="L93">                        return;</span>
    +                    }
    +<span class="nc" id="L95">                    List&lt;SimpleGrantedAuthority&gt; authorities =</span>
    +<span class="nc" id="L96">                            user.get().getAuthorities().stream()</span>
    +<span class="nc" id="L97">                                    .map(</span>
    +                                            authority -&gt;
    +<span class="nc" id="L99">                                                    new SimpleGrantedAuthority(</span>
    +<span class="nc" id="L100">                                                            authority.getAuthority()))</span>
    +<span class="nc" id="L101">                                    .toList();</span>
    +<span class="nc" id="L102">                    authentication = new ApiKeyAuthenticationToken(user.get(), apiKey, authorities);</span>
    +<span class="nc" id="L103">                    SecurityContextHolder.getContext().setAuthentication(authentication);</span>
    +<span class="nc" id="L104">                } catch (AuthenticationException e) {</span>
    +                    // If API key authentication fails, deny the request
    +<span class="nc" id="L106">                    response.setStatus(HttpStatus.UNAUTHORIZED.value());</span>
    +<span class="nc" id="L107">                    response.getWriter().write(&quot;Invalid API Key.&quot;);</span>
    +<span class="nc" id="L108">                    return;</span>
    +<span class="nc" id="L109">                }</span>
    +            }
    +        }
    +
    +        // If we still don't have any authentication, deny the request
    +<span class="nc bnc" id="L114" title="All 4 branches missed.">        if (authentication == null || !authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L115">            String method = request.getMethod();</span>
    +<span class="nc" id="L116">            String contextPath = request.getContextPath();</span>
    +
    +<span class="nc bnc" id="L118" title="All 4 branches missed.">            if (&quot;GET&quot;.equalsIgnoreCase(method) &amp;&amp; !(contextPath + &quot;/login&quot;).equals(requestURI)) {</span>
    +<span class="nc" id="L119">                response.sendRedirect(contextPath + &quot;/login&quot;); // redirect to the login page</span>
    +<span class="nc" id="L120">                return;</span>
    +            } else {
    +<span class="nc" id="L122">                response.setStatus(HttpStatus.UNAUTHORIZED.value());</span>
    +<span class="nc" id="L123">                response.getWriter()</span>
    +<span class="nc" id="L124">                        .write(</span>
    +                                &quot;Authentication required. Please provide a X-API-KEY in request&quot;
    +                                        + &quot; header.\n&quot;
    +                                        + &quot;This is found in Settings -&gt; Account Settings -&gt; API Key\n&quot;
    +                                        + &quot;Alternatively you can disable authentication if this is&quot;
    +                                        + &quot; unexpected&quot;);
    +<span class="nc" id="L130">                return;</span>
    +            }
    +        }
    +
    +        // Check if the authenticated user is disabled and invalidate their session if so
    +<span class="nc bnc" id="L135" title="All 4 branches missed.">        if (authentication != null &amp;&amp; authentication.isAuthenticated()) {</span>
    +
    +<span class="nc" id="L137">            Security securityProp = applicationProperties.getSecurity();</span>
    +<span class="nc" id="L138">            LoginMethod loginMethod = LoginMethod.UNKNOWN;</span>
    +
    +<span class="nc" id="L140">            boolean blockRegistration = false;</span>
    +
    +            // Extract username and determine the login method
    +<span class="nc" id="L143">            Object principal = authentication.getPrincipal();</span>
    +<span class="nc" id="L144">            String username = null;</span>
    +<span class="nc bnc" id="L145" title="All 2 branches missed.">            if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L146">                username = detailsUser.getUsername();</span>
    +<span class="nc" id="L147">                loginMethod = LoginMethod.USERDETAILS;</span>
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">            } else if (principal instanceof OAuth2User oAuth2User) {</span>
    +<span class="nc" id="L149">                username = oAuth2User.getName();</span>
    +<span class="nc" id="L150">                loginMethod = LoginMethod.OAUTH2USER;</span>
    +<span class="nc" id="L151">                OAUTH2 oAuth = securityProp.getOauth2();</span>
    +<span class="nc bnc" id="L152" title="All 4 branches missed.">                blockRegistration = oAuth != null &amp;&amp; oAuth.getBlockRegistration();</span>
    +<span class="nc bnc" id="L153" title="All 2 branches missed.">            } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {</span>
    +<span class="nc" id="L154">                username = saml2User.name();</span>
    +<span class="nc" id="L155">                loginMethod = LoginMethod.SAML2USER;</span>
    +<span class="nc" id="L156">                SAML2 saml2 = securityProp.getSaml2();</span>
    +<span class="nc bnc" id="L157" title="All 4 branches missed.">                blockRegistration = saml2 != null &amp;&amp; saml2.getBlockRegistration();</span>
    +<span class="nc bnc" id="L158" title="All 2 branches missed.">            } else if (principal instanceof String stringUser) {</span>
    +<span class="nc" id="L159">                username = stringUser;</span>
    +<span class="nc" id="L160">                loginMethod = LoginMethod.STRINGUSER;</span>
    +            }
    +
    +            // Retrieve all active sessions for the user
    +<span class="nc" id="L164">            List&lt;SessionInformation&gt; sessionsInformations =</span>
    +<span class="nc" id="L165">                    sessionPersistentRegistry.getAllSessions(principal, false);</span>
    +
    +            // Check if the user exists, is disabled, or needs session invalidation
    +<span class="nc bnc" id="L168" title="All 2 branches missed.">            if (username != null) {</span>
    +<span class="nc" id="L169">                log.debug(&quot;Validating user: {}&quot;, username);</span>
    +<span class="nc" id="L170">                boolean isUserExists = userService.usernameExistsIgnoreCase(username);</span>
    +<span class="nc" id="L171">                boolean isUserDisabled = userService.isUserDisabled(username);</span>
    +
    +<span class="nc" id="L173">                boolean notSsoLogin =</span>
    +<span class="nc bnc" id="L174" title="All 2 branches missed.">                        !LoginMethod.OAUTH2USER.equals(loginMethod)</span>
    +<span class="nc bnc" id="L175" title="All 2 branches missed.">                                &amp;&amp; !LoginMethod.SAML2USER.equals(loginMethod);</span>
    +
    +                // Block user registration if not allowed by configuration
    +<span class="nc bnc" id="L178" title="All 4 branches missed.">                if (blockRegistration &amp;&amp; !isUserExists) {</span>
    +<span class="nc" id="L179">                    log.warn(&quot;Blocked registration for OAuth2/SAML user: {}&quot;, username);</span>
    +<span class="nc" id="L180">                    response.sendRedirect(</span>
    +<span class="nc" id="L181">                            request.getContextPath() + &quot;/logout?oAuth2AdminBlockedUser=true&quot;);</span>
    +<span class="nc" id="L182">                    return;</span>
    +                }
    +
    +                // Expire sessions and logout if the user does not exist or is disabled
    +<span class="nc bnc" id="L186" title="All 4 branches missed.">                if (!isUserExists || isUserDisabled) {</span>
    +<span class="nc" id="L187">                    log.info(</span>
    +                            &quot;Invalidating session for disabled or non-existent user: {}&quot;, username);
    +<span class="nc bnc" id="L189" title="All 2 branches missed.">                    for (SessionInformation sessionsInformation : sessionsInformations) {</span>
    +<span class="nc" id="L190">                        sessionsInformation.expireNow();</span>
    +<span class="nc" id="L191">                        sessionPersistentRegistry.expireSession(sessionsInformation.getSessionId());</span>
    +<span class="nc" id="L192">                    }</span>
    +                }
    +
    +                // Redirect to logout if credentials are invalid
    +<span class="nc bnc" id="L196" title="All 4 branches missed.">                if (!isUserExists &amp;&amp; notSsoLogin) {</span>
    +<span class="nc" id="L197">                    response.sendRedirect(request.getContextPath() + &quot;/logout?badCredentials=true&quot;);</span>
    +<span class="nc" id="L198">                    return;</span>
    +                }
    +<span class="nc bnc" id="L200" title="All 2 branches missed.">                if (isUserDisabled) {</span>
    +<span class="nc" id="L201">                    response.sendRedirect(request.getContextPath() + &quot;/logout?userIsDisabled=true&quot;);</span>
    +<span class="nc" id="L202">                    return;</span>
    +                }
    +            }
    +        }
    +
    +<span class="nc" id="L207">        filterChain.doFilter(request, response);</span>
    +<span class="nc" id="L208">    }</span>
    +
    +<span class="nc" id="L210">    private enum LoginMethod {</span>
    +<span class="nc" id="L211">        USERDETAILS(&quot;UserDetails&quot;),</span>
    +<span class="nc" id="L212">        OAUTH2USER(&quot;OAuth2User&quot;),</span>
    +<span class="nc" id="L213">        STRINGUSER(&quot;StringUser&quot;),</span>
    +<span class="nc" id="L214">        UNKNOWN(&quot;Unknown&quot;),</span>
    +<span class="nc" id="L215">        SAML2USER(&quot;Saml2User&quot;);</span>
    +
    +        private String method;
    +
    +<span class="nc" id="L219">        LoginMethod(String method) {</span>
    +<span class="nc" id="L220">            this.method = method;</span>
    +<span class="nc" id="L221">        }</span>
    +
    +        @Override
    +        public String toString() {
    +<span class="nc" id="L225">            return method;</span>
    +        }
    +    }
    +
    +    @Override
    +    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
    +<span class="nc" id="L231">        String uri = request.getRequestURI();</span>
    +<span class="nc" id="L232">        String contextPath = request.getContextPath();</span>
    +<span class="nc" id="L233">        String[] permitAllPatterns = {</span>
    +            contextPath + &quot;/login&quot;,
    +            contextPath + &quot;/register&quot;,
    +            contextPath + &quot;/error&quot;,
    +            contextPath + &quot;/images/&quot;,
    +            contextPath + &quot;/public/&quot;,
    +            contextPath + &quot;/css/&quot;,
    +            contextPath + &quot;/fonts/&quot;,
    +            contextPath + &quot;/js/&quot;,
    +            contextPath + &quot;/pdfjs/&quot;,
    +            contextPath + &quot;/pdfjs-legacy/&quot;,
    +            contextPath + &quot;/api/v1/info/status&quot;,
    +            contextPath + &quot;/site.webmanifest&quot;
    +        };
    +
    +<span class="nc bnc" id="L248" title="All 2 branches missed.">        for (String pattern : permitAllPatterns) {</span>
    +<span class="nc bnc" id="L249" title="All 2 branches missed.">            if (uri.startsWith(pattern)</span>
    +<span class="nc bnc" id="L250" title="All 2 branches missed.">                    || uri.endsWith(&quot;.svg&quot;)</span>
    +<span class="nc bnc" id="L251" title="All 2 branches missed.">                    || uri.endsWith(&quot;.png&quot;)</span>
    +<span class="nc bnc" id="L252" title="All 2 branches missed.">                    || uri.endsWith(&quot;.ico&quot;)) {</span>
    +<span class="nc" id="L253">                return true;</span>
    +            }
    +        }
    +
    +<span class="nc" id="L257">        return false;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/UserBasedRateLimitingFilter.html b/testResult/html/stirling.software.SPDF.config.security/UserBasedRateLimitingFilter.html
    new file mode 100644
    index 000000000..b7dd50bb0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/UserBasedRateLimitingFilter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserBasedRateLimitingFilter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">UserBasedRateLimitingFilter</span></div><h1>UserBasedRateLimitingFilter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">197 of 197</td><td class="ctr2">0%</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">53</td><td class="ctr2">53</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a1"><a href="UserBasedRateLimitingFilter.java.html#L47" class="el_method">doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="87" alt="87"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">25</td><td class="ctr2" id="i0">25</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a4"><a href="UserBasedRateLimitingFilter.java.html#L119" class="el_method">processRequest(int, String, Map, HttpServletRequest, HttpServletResponse, FilterChain)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="47" alt="47"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h1">12</td><td class="ctr2" id="i1">12</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="UserBasedRateLimitingFilter.java.html#L99" class="el_method">getRoleFromAuthentication(Authentication)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="27" alt="27"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a5"><a href="UserBasedRateLimitingFilter.java.html#L32" class="el_method">UserBasedRateLimitingFilter(boolean)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="16" alt="16"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">5</td><td class="ctr2" id="i3">5</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="UserBasedRateLimitingFilter.java.html#L138" class="el_method">createUserBucket(int)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="16" alt="16"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">5</td><td class="ctr2" id="i4">5</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="UserBasedRateLimitingFilter.java.html#L119" class="el_method">lambda$processRequest$0(int, String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/UserBasedRateLimitingFilter.java.html b/testResult/html/stirling.software.SPDF.config.security/UserBasedRateLimitingFilter.java.html
    new file mode 100644
    index 000000000..2952e7d9f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/UserBasedRateLimitingFilter.java.html
    @@ -0,0 +1,145 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserBasedRateLimitingFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">UserBasedRateLimitingFilter.java</span></div><h1>UserBasedRateLimitingFilter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.io.IOException;
    +import java.time.Duration;
    +import java.util.Map;
    +import java.util.concurrent.ConcurrentHashMap;
    +
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.core.GrantedAuthority;
    +import org.springframework.security.core.context.SecurityContextHolder;
    +import org.springframework.security.core.userdetails.UserDetails;
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.filter.OncePerRequestFilter;
    +
    +import io.github.bucket4j.Bandwidth;
    +import io.github.bucket4j.Bucket;
    +import io.github.bucket4j.ConsumptionProbe;
    +import io.github.pixee.security.Newlines;
    +
    +import jakarta.servlet.FilterChain;
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import stirling.software.SPDF.model.Role;
    +
    +@Component
    +public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
    +
    +<span class="nc" id="L32">    private final Map&lt;String, Bucket&gt; apiBuckets = new ConcurrentHashMap&lt;&gt;();</span>
    +
    +<span class="nc" id="L34">    private final Map&lt;String, Bucket&gt; webBuckets = new ConcurrentHashMap&lt;&gt;();</span>
    +
    +    @Qualifier(&quot;rateLimit&quot;)
    +    private final boolean rateLimit;
    +
    +<span class="nc" id="L39">    public UserBasedRateLimitingFilter(@Qualifier(&quot;rateLimit&quot;) boolean rateLimit) {</span>
    +<span class="nc" id="L40">        this.rateLimit = rateLimit;</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    @Override
    +    protected void doFilterInternal(
    +            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    +            throws ServletException, IOException {
    +<span class="nc bnc" id="L47" title="All 2 branches missed.">        if (!rateLimit) {</span>
    +            // If rateLimit is not enabled, just pass all requests without rate limiting
    +<span class="nc" id="L49">            filterChain.doFilter(request, response);</span>
    +<span class="nc" id="L50">            return;</span>
    +        }
    +<span class="nc" id="L52">        String method = request.getMethod();</span>
    +<span class="nc bnc" id="L53" title="All 2 branches missed.">        if (!&quot;POST&quot;.equalsIgnoreCase(method)) {</span>
    +            // If the request is not a POST, just pass it through without rate limiting
    +<span class="nc" id="L55">            filterChain.doFilter(request, response);</span>
    +<span class="nc" id="L56">            return;</span>
    +        }
    +<span class="nc" id="L58">        String identifier = null;</span>
    +        // Check for API key in the request headers
    +<span class="nc" id="L60">        String apiKey = request.getHeader(&quot;X-API-KEY&quot;);</span>
    +<span class="nc bnc" id="L61" title="All 4 branches missed.">        if (apiKey != null &amp;&amp; !apiKey.trim().isEmpty()) {</span>
    +<span class="nc" id="L62">            identifier = // Prefix to distinguish between API keys and usernames</span>
    +                    &quot;API_KEY_&quot; + apiKey;
    +        } else {
    +<span class="nc" id="L65">            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();</span>
    +<span class="nc bnc" id="L66" title="All 4 branches missed.">            if (authentication != null &amp;&amp; authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L67">                UserDetails userDetails = (UserDetails) authentication.getPrincipal();</span>
    +<span class="nc" id="L68">                identifier = userDetails.getUsername();</span>
    +            }
    +        }
    +        // If neither API key nor an authenticated user is present, use IP address
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">        if (identifier == null) {</span>
    +<span class="nc" id="L73">            identifier = request.getRemoteAddr();</span>
    +        }
    +<span class="nc" id="L75">        Role userRole =</span>
    +<span class="nc" id="L76">                getRoleFromAuthentication(SecurityContextHolder.getContext().getAuthentication());</span>
    +<span class="nc bnc" id="L77" title="All 2 branches missed.">        if (request.getHeader(&quot;X-API-KEY&quot;) != null) {</span>
    +            // It's an API call
    +<span class="nc" id="L79">            processRequest(</span>
    +<span class="nc" id="L80">                    userRole.getApiCallsPerDay(),</span>
    +                    identifier,
    +                    apiBuckets,
    +                    request,
    +                    response,
    +                    filterChain);
    +        } else {
    +            // It's a Web UI call
    +<span class="nc" id="L88">            processRequest(</span>
    +<span class="nc" id="L89">                    userRole.getWebCallsPerDay(),</span>
    +                    identifier,
    +                    webBuckets,
    +                    request,
    +                    response,
    +                    filterChain);
    +        }
    +<span class="nc" id="L96">    }</span>
    +
    +    private Role getRoleFromAuthentication(Authentication authentication) {
    +<span class="nc bnc" id="L99" title="All 4 branches missed.">        if (authentication != null &amp;&amp; authentication.isAuthenticated()) {</span>
    +<span class="nc bnc" id="L100" title="All 2 branches missed.">            for (GrantedAuthority authority : authentication.getAuthorities()) {</span>
    +                try {
    +<span class="nc" id="L102">                    return Role.fromString(authority.getAuthority());</span>
    +<span class="nc" id="L103">                } catch (IllegalArgumentException ex) {</span>
    +                    // Ignore and continue to next authority.
    +                }
    +<span class="nc" id="L106">            }</span>
    +        }
    +<span class="nc" id="L108">        throw new IllegalStateException(&quot;User does not have a valid role.&quot;);</span>
    +    }
    +
    +    private void processRequest(
    +            int limitPerDay,
    +            String identifier,
    +            Map&lt;String, Bucket&gt; buckets,
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            FilterChain filterChain)
    +            throws IOException, ServletException {
    +<span class="nc" id="L119">        Bucket userBucket = buckets.computeIfAbsent(identifier, k -&gt; createUserBucket(limitPerDay));</span>
    +<span class="nc" id="L120">        ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);</span>
    +<span class="nc bnc" id="L121" title="All 2 branches missed.">        if (probe.isConsumed()) {</span>
    +<span class="nc" id="L122">            response.setHeader(</span>
    +                    &quot;X-Rate-Limit-Remaining&quot;,
    +<span class="nc" id="L124">                    Newlines.stripAll(Long.toString(probe.getRemainingTokens())));</span>
    +<span class="nc" id="L125">            filterChain.doFilter(request, response);</span>
    +        } else {
    +<span class="nc" id="L127">            long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;</span>
    +<span class="nc" id="L128">            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());</span>
    +<span class="nc" id="L129">            response.setHeader(</span>
    +                    &quot;X-Rate-Limit-Retry-After-Seconds&quot;,
    +<span class="nc" id="L131">                    Newlines.stripAll(String.valueOf(waitForRefill)));</span>
    +<span class="nc" id="L132">            response.getWriter().write(&quot;Rate limit exceeded for POST requests.&quot;);</span>
    +        }
    +<span class="nc" id="L134">    }</span>
    +
    +    private Bucket createUserBucket(int limitPerDay) {
    +        Bandwidth limit =
    +<span class="nc" id="L138">                Bandwidth.builder()</span>
    +<span class="nc" id="L139">                        .capacity(limitPerDay)</span>
    +<span class="nc" id="L140">                        .refillIntervally(limitPerDay, Duration.ofDays(1))</span>
    +<span class="nc" id="L141">                        .build();</span>
    +<span class="nc" id="L142">        return Bucket.builder().addLimit(limit).build();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/UserService.html b/testResult/html/stirling.software.SPDF.config.security/UserService.html
    new file mode 100644
    index 000000000..f388ffbfc
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/UserService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_class">UserService</span></div><h1>UserService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">953 of 953</td><td class="ctr2">0%</td><td class="bar">90 of 90</td><td class="ctr2">0%</td><td class="ctr1">96</td><td class="ctr2">96</td><td class="ctr1">239</td><td class="ctr2">239</td><td class="ctr1">51</td><td class="ctr2">51</td></tr></tfoot><tbody><tr><td id="a21"><a href="UserService.java.html#L400" class="el_method">invalidateUserSessions(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="77" alt="77"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h0">16</td><td class="ctr2" id="i0">16</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a41"><a href="UserService.java.html#L236" class="el_method">saveUser(String, String, boolean, boolean)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="76" height="10" title="49" alt="49"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f12">2</td><td class="ctr2" id="g12">2</td><td class="ctr1" id="h1">12</td><td class="ctr2" id="i1">12</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a43"><a href="UserService.java.html#L215" class="el_method">saveUser(String, String, String, boolean)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="74" height="10" title="48" alt="48"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f13">2</td><td class="ctr2" id="g13">2</td><td class="ctr1" id="h2">12</td><td class="ctr2" id="i2">12</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a15"><a href="UserService.java.html#L422" class="el_method">getCurrentUsername()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="73" height="10" title="47" alt="47"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h4">11</td><td class="ctr2" id="i4">11</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a40"><a href="UserService.java.html#L200" class="el_method">saveUser(String, String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="71" height="10" title="46" alt="46"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f14">2</td><td class="ctr2" id="g14">2</td><td class="ctr1" id="h5">11</td><td class="ctr2" id="i5">11</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a39"><a href="UserService.java.html#L185" class="el_method">saveUser(String, AuthenticationType, String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="65" height="10" title="42" alt="42"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f15">2</td><td class="ctr2" id="g15">2</td><td class="ctr1" id="h6">11</td><td class="ctr2" id="i6">11</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="UserService.java.html#L251" class="el_method">deleteUser(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="59" height="10" title="38" alt="38"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="6" alt="6"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h9">9</td><td class="ctr2" id="i9">9</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a46"><a href="UserService.java.html#L281" class="el_method">updateUserSettings(String, Map)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="57" height="10" title="37" alt="37"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h3">12</td><td class="ctr2" id="i3">12</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a32"><a href="UserService.java.html#L458" class="el_method">lambda$syncCustomApiUser$5(String, String)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="57" height="10" title="37" alt="37"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h7">10</td><td class="ctr2" id="i7">10</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a25"><a href="UserService.java.html#L361" class="el_method">isUsernameValid(String)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="36" alt="36"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="6" alt="6"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f3">4</td><td class="ctr2" id="g3">4</td><td class="ctr1" id="h10">9</td><td class="ctr2" id="i10">9</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a45"><a href="UserService.java.html#L439" class="el_method">syncCustomApiUser(String)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="33" alt="33"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f6">3</td><td class="ctr2" id="g6">3</td><td class="ctr1" id="h8">10</td><td class="ctr2" id="i8">10</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a49"><a href="UserService.java.html#L58" class="el_method">UserService(UserRepository, AuthorityRepository, PasswordEncoder, MessageSource, SessionPersistentRegistry, DatabaseInterface, ApplicationProperties)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="24" alt="24"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h11">9</td><td class="ctr2" id="i11">9</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a13"><a href="UserService.java.html#L95" class="el_method">getAuthentication(String)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="23" alt="23"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f16">2</td><td class="ctr2" id="g16">2</td><td class="ctr1" id="h16">5</td><td class="ctr2" id="i16">5</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a20"><a href="UserService.java.html#L272" class="el_method">hasUsers()</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="22" alt="22"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f7">3</td><td class="ctr2" id="g7">3</td><td class="ctr1" id="h19">4</td><td class="ctr2" id="i19">4</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a5"><a href="UserService.java.html#L317" class="el_method">changeUsername(User, String)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="22" alt="22"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d17"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f17">2</td><td class="ctr2" id="g17">2</td><td class="ctr1" id="h14">6</td><td class="ctr2" id="i14">6</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a0"><a href="UserService.java.html#L123" class="el_method">addApiKeyToUser(String)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="21" alt="21"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g25">1</td><td class="ctr1" id="h13">7</td><td class="ctr2" id="i13">7</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a12"><a href="UserService.java.html#L139" class="el_method">getApiKeyForUser(String)</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="21" alt="21"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f8">3</td><td class="ctr2" id="g8">3</td><td class="ctr1" id="h15">6</td><td class="ctr2" id="i15">6</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a37"><a href="UserService.java.html#L176" class="el_method">saveUser(Optional, String)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="21" alt="21"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d18"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f18">2</td><td class="ctr2" id="g18">2</td><td class="ctr1" id="h20">4</td><td class="ctr2" id="i20">4</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a35"><a href="UserService.java.html#L82" class="el_method">processSSOPostLogin(String, boolean)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="20" alt="20"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="6" alt="6"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f4">4</td><td class="ctr2" id="g4">4</td><td class="ctr1" id="h12">8</td><td class="ctr2" id="i12">8</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a22"><a href="UserService.java.html#L389" class="el_method">isAuthenticationTypeByUsername(String, AuthenticationType)</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="19" alt="19"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f9">3</td><td class="ctr2" id="g9">3</td><td class="ctr1" id="h27">3</td><td class="ctr2" id="i27">3</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a50"><a href="UserService.java.html#L166" class="el_method">validateApiKeyForUser(String, String)</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="18" alt="18"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e17">0%</td><td class="ctr1" id="f10">3</td><td class="ctr2" id="g10">3</td><td class="ctr1" id="h30">2</td><td class="ctr2" id="i30">2</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a31"><a href="UserService.java.html#L449" class="el_method">lambda$syncCustomApiUser$4(Optional, String, User)</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="18" alt="18"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d19"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e18">0%</td><td class="ctr1" id="f19">2</td><td class="ctr2" id="g19">2</td><td class="ctr1" id="h17">5</td><td class="ctr2" id="i17">5</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a3"><a href="UserService.java.html#L341" class="el_method">changeRole(User, String)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="16" alt="16"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d26"/><td class="ctr2" id="e26">n/a</td><td class="ctr1" id="f26">1</td><td class="ctr2" id="g26">1</td><td class="ctr1" id="h18">5</td><td class="ctr2" id="i18">5</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a19"><a href="UserService.java.html#L383" class="el_method">hasPassword(String)</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="16" alt="16"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e19">0%</td><td class="ctr1" id="f11">3</td><td class="ctr2" id="g11">3</td><td class="ctr1" id="h31">2</td><td class="ctr2" id="i31">2</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a2"><a href="UserService.java.html#L327" class="el_method">changePassword(User, String)</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="15" alt="15"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d27"/><td class="ctr2" id="e27">n/a</td><td class="ctr1" id="f27">1</td><td class="ctr2" id="g27">1</td><td class="ctr1" id="h21">4</td><td class="ctr2" id="i21">4</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr><tr><td id="a4"><a href="UserService.java.html#L349" class="el_method">changeUserEnabled(User, Boolean)</a></td><td class="bar" id="b25"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="13" alt="13"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d28"/><td class="ctr2" id="e28">n/a</td><td class="ctr1" id="f28">1</td><td class="ctr2" id="g28">1</td><td class="ctr1" id="h22">4</td><td class="ctr2" id="i22">4</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k25">1</td></tr><tr><td id="a24"><a href="UserService.java.html#L395" class="el_method">isUserDisabled(String)</a></td><td class="bar" id="b26"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="13" alt="13"/></td><td class="ctr2" id="c26">0%</td><td class="bar" id="d29"/><td class="ctr2" id="e29">n/a</td><td class="ctr1" id="f29">1</td><td class="ctr2" id="g29">1</td><td class="ctr1" id="h32">2</td><td class="ctr2" id="i32">2</td><td class="ctr1" id="j26">1</td><td class="ctr2" id="k26">1</td></tr><tr><td id="a33"><a href="UserService.java.html#L157" class="el_method">loadUserByApiKey(String)</a></td><td class="bar" id="b27"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="12" alt="12"/></td><td class="ctr2" id="c27">0%</td><td class="bar" id="d20"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e20">0%</td><td class="ctr1" id="f20">2</td><td class="ctr2" id="g20">2</td><td class="ctr1" id="h23">4</td><td class="ctr2" id="i23">4</td><td class="ctr1" id="j27">1</td><td class="ctr2" id="k27">1</td></tr><tr><td id="a1"><a href="UserService.java.html#L334" class="el_method">changeFirstUse(User, boolean)</a></td><td class="bar" id="b28"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="12" alt="12"/></td><td class="ctr2" id="c28">0%</td><td class="bar" id="d30"/><td class="ctr2" id="e30">n/a</td><td class="ctr1" id="f30">1</td><td class="ctr2" id="g30">1</td><td class="ctr1" id="h24">4</td><td class="ctr2" id="i24">4</td><td class="ctr1" id="j28">1</td><td class="ctr2" id="k28">1</td></tr><tr><td id="a11"><a href="UserService.java.html#L116" class="el_method">generateApiKey()</a></td><td class="bar" id="b29"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="11" alt="11"/></td><td class="ctr2" id="c29">0%</td><td class="bar" id="d21"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e21">0%</td><td class="ctr1" id="f21">2</td><td class="ctr2" id="g21">2</td><td class="ctr1" id="h25">4</td><td class="ctr2" id="i25">4</td><td class="ctr1" id="j29">1</td><td class="ctr2" id="k29">1</td></tr><tr><td id="a30"><a href="UserService.java.html#L74" class="el_method">lambda$migrateOauth2ToSSO$0(User)</a></td><td class="bar" id="b30"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="9" alt="9"/></td><td class="ctr2" id="c30">0%</td><td class="bar" id="d31"/><td class="ctr2" id="e31">n/a</td><td class="ctr1" id="f31">1</td><td class="ctr2" id="g31">1</td><td class="ctr1" id="h28">3</td><td class="ctr2" id="i28">3</td><td class="ctr1" id="j30">1</td><td class="ctr2" id="k30">1</td></tr><tr><td id="a34"><a href="UserService.java.html#L70" class="el_method">migrateOauth2ToSSO()</a></td><td class="bar" id="b31"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="8" alt="8"/></td><td class="ctr2" id="c31">0%</td><td class="bar" id="d32"/><td class="ctr2" id="e32">n/a</td><td class="ctr1" id="f32">1</td><td class="ctr2" id="g32">1</td><td class="ctr1" id="h26">4</td><td class="ctr2" id="i26">4</td><td class="ctr1" id="j31">1</td><td class="ctr2" id="k31">1</td></tr><tr><td id="a29"><a href="UserService.java.html#L396" class="el_method">lambda$isUserDisabled$3(User)</a></td><td class="bar" id="b32"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="8" alt="8"/></td><td class="ctr2" id="c32">0%</td><td class="bar" id="d22"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e22">0%</td><td class="ctr1" id="f22">2</td><td class="ctr2" id="g22">2</td><td class="ctr1" id="h36">1</td><td class="ctr2" id="i36">1</td><td class="ctr1" id="j32">1</td><td class="ctr2" id="k32">1</td></tr><tr><td id="a14"><a href="UserService.java.html#L108" class="el_method">getAuthorities(User)</a></td><td class="bar" id="b33"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="7" alt="7"/></td><td class="ctr2" id="c33">0%</td><td class="bar" id="d33"/><td class="ctr2" id="e33">n/a</td><td class="ctr1" id="f33">1</td><td class="ctr2" id="g33">1</td><td class="ctr1" id="h29">3</td><td class="ctr2" id="i29">3</td><td class="ctr1" id="j33">1</td><td class="ctr2" id="k33">1</td></tr><tr><td id="a38"><a href="UserService.java.html#L172" class="el_method">saveUser(String, AuthenticationType)</a></td><td class="bar" id="b34"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="7" alt="7"/></td><td class="ctr2" id="c34">0%</td><td class="bar" id="d34"/><td class="ctr2" id="e34">n/a</td><td class="ctr1" id="f34">1</td><td class="ctr2" id="g34">1</td><td class="ctr1" id="h33">2</td><td class="ctr2" id="i33">2</td><td class="ctr1" id="j34">1</td><td class="ctr2" id="k34">1</td></tr><tr><td id="a42"><a href="UserService.java.html#L231" class="el_method">saveUser(String, String, String)</a></td><td class="bar" id="b35"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="7" alt="7"/></td><td class="ctr2" id="c35">0%</td><td class="bar" id="d35"/><td class="ctr2" id="e35">n/a</td><td class="ctr1" id="f35">1</td><td class="ctr2" id="g35">1</td><td class="ctr1" id="h34">2</td><td class="ctr2" id="i34">2</td><td class="ctr1" id="j35">1</td><td class="ctr2" id="k35">1</td></tr><tr><td id="a10"><a href="UserService.java.html#L309" class="el_method">findRole(User)</a></td><td class="bar" id="b36"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="7" alt="7"/></td><td class="ctr2" id="c36">0%</td><td class="bar" id="d36"/><td class="ctr2" id="e36">n/a</td><td class="ctr1" id="f36">1</td><td class="ctr2" id="g36">1</td><td class="ctr1" id="h37">1</td><td class="ctr2" id="i37">1</td><td class="ctr1" id="j36">1</td><td class="ctr2" id="k36">1</td></tr><tr><td id="a23"><a href="UserService.java.html#L355" class="el_method">isPasswordCorrect(User, String)</a></td><td class="bar" id="b37"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="7" alt="7"/></td><td class="ctr2" id="c37">0%</td><td class="bar" id="d37"/><td class="ctr2" id="e37">n/a</td><td class="ctr1" id="f37">1</td><td class="ctr2" id="g37">1</td><td class="ctr1" id="h38">1</td><td class="ctr2" id="i38">1</td><td class="ctr1" id="j37">1</td><td class="ctr2" id="k37">1</td></tr><tr><td id="a16"><a href="UserService.java.html#L378" class="el_method">getInvalidUsernameMessage()</a></td><td class="bar" id="b38"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="7" alt="7"/></td><td class="ctr2" id="c38">0%</td><td class="bar" id="d38"/><td class="ctr2" id="e38">n/a</td><td class="ctr1" id="f38">1</td><td class="ctr2" id="g38">1</td><td class="ctr1" id="h35">2</td><td class="ctr2" id="i35">2</td><td class="ctr1" id="j38">1</td><td class="ctr2" id="k38">1</td></tr><tr><td id="a26"><a href="UserService.java.html#L149" class="el_method">isValidApiKey(String)</a></td><td class="bar" id="b39"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="6" alt="6"/></td><td class="ctr2" id="c39">0%</td><td class="bar" id="d39"/><td class="ctr2" id="e39">n/a</td><td class="ctr1" id="f39">1</td><td class="ctr2" id="g39">1</td><td class="ctr1" id="h39">1</td><td class="ctr2" id="i39">1</td><td class="ctr1" id="j39">1</td><td class="ctr2" id="k39">1</td></tr><tr><td id="a28"><a href="UserService.java.html#L109" class="el_method">lambda$getAuthorities$1(Authority)</a></td><td class="bar" id="b40"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="6" alt="6"/></td><td class="ctr2" id="c40">0%</td><td class="bar" id="d40"/><td class="ctr2" id="e40">n/a</td><td class="ctr1" id="f40">1</td><td class="ctr2" id="g40">1</td><td class="ctr1" id="h40">1</td><td class="ctr2" id="i40">1</td><td class="ctr1" id="j40">1</td><td class="ctr2" id="k40">1</td></tr><tr><td id="a18"><a href="UserService.java.html#L153" class="el_method">getUserByApiKey(String)</a></td><td class="bar" id="b41"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c41">0%</td><td class="bar" id="d41"/><td class="ctr2" id="e41">n/a</td><td class="ctr1" id="f41">1</td><td class="ctr2" id="g41">1</td><td class="ctr1" id="h41">1</td><td class="ctr2" id="i41">1</td><td class="ctr1" id="j41">1</td><td class="ctr2" id="k41">1</td></tr><tr><td id="a47"><a href="UserService.java.html#L264" class="el_method">usernameExists(String)</a></td><td class="bar" id="b42"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c42">0%</td><td class="bar" id="d42"/><td class="ctr2" id="e42">n/a</td><td class="ctr1" id="f42">1</td><td class="ctr2" id="g42">1</td><td class="ctr1" id="h42">1</td><td class="ctr2" id="i42">1</td><td class="ctr1" id="j42">1</td><td class="ctr2" id="k42">1</td></tr><tr><td id="a48"><a href="UserService.java.html#L268" class="el_method">usernameExistsIgnoreCase(String)</a></td><td class="bar" id="b43"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c43">0%</td><td class="bar" id="d43"/><td class="ctr2" id="e43">n/a</td><td class="ctr1" id="f43">1</td><td class="ctr2" id="g43">1</td><td class="ctr1" id="h43">1</td><td class="ctr2" id="i43">1</td><td class="ctr1" id="j43">1</td><td class="ctr2" id="k43">1</td></tr><tr><td id="a7"><a href="UserService.java.html#L297" class="el_method">findByUsername(String)</a></td><td class="bar" id="b44"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c44">0%</td><td class="bar" id="d44"/><td class="ctr2" id="e44">n/a</td><td class="ctr1" id="f44">1</td><td class="ctr2" id="g44">1</td><td class="ctr1" id="h44">1</td><td class="ctr2" id="i44">1</td><td class="ctr1" id="j44">1</td><td class="ctr2" id="k44">1</td></tr><tr><td id="a8"><a href="UserService.java.html#L301" class="el_method">findByUsernameIgnoreCase(String)</a></td><td class="bar" id="b45"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c45">0%</td><td class="bar" id="d45"/><td class="ctr2" id="e45">n/a</td><td class="ctr1" id="f45">1</td><td class="ctr2" id="g45">1</td><td class="ctr1" id="h45">1</td><td class="ctr2" id="i45">1</td><td class="ctr1" id="j45">1</td><td class="ctr2" id="k45">1</td></tr><tr><td id="a9"><a href="UserService.java.html#L305" class="el_method">findByUsernameIgnoreCaseWithSettings(String)</a></td><td class="bar" id="b46"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c46">0%</td><td class="bar" id="d46"/><td class="ctr2" id="e46">n/a</td><td class="ctr1" id="f46">1</td><td class="ctr2" id="g46">1</td><td class="ctr1" id="h46">1</td><td class="ctr2" id="i46">1</td><td class="ctr1" id="j46">1</td><td class="ctr2" id="k46">1</td></tr><tr><td id="a27"><a href="UserService.java.html#L141" class="el_method">lambda$getApiKeyForUser$2()</a></td><td class="bar" id="b47"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="5" alt="5"/></td><td class="ctr2" id="c47">0%</td><td class="bar" id="d47"/><td class="ctr2" id="e47">n/a</td><td class="ctr1" id="f47">1</td><td class="ctr2" id="g47">1</td><td class="ctr1" id="h47">1</td><td class="ctr2" id="i47">1</td><td class="ctr1" id="j47">1</td><td class="ctr2" id="k47">1</td></tr><tr><td id="a36"><a href="UserService.java.html#L135" class="el_method">refreshApiKeyForUser(String)</a></td><td class="bar" id="b48"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c48">0%</td><td class="bar" id="d48"/><td class="ctr2" id="e48">n/a</td><td class="ctr1" id="f48">1</td><td class="ctr2" id="g48">1</td><td class="ctr1" id="h48">1</td><td class="ctr2" id="i48">1</td><td class="ctr1" id="j48">1</td><td class="ctr2" id="k48">1</td></tr><tr><td id="a17"><a href="UserService.java.html#L478" class="el_method">getTotalUsersCount()</a></td><td class="bar" id="b49"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c49">0%</td><td class="bar" id="d49"/><td class="ctr2" id="e49">n/a</td><td class="ctr1" id="f49">1</td><td class="ctr2" id="g49">1</td><td class="ctr1" id="h49">1</td><td class="ctr2" id="i49">1</td><td class="ctr1" id="j49">1</td><td class="ctr2" id="k49">1</td></tr><tr><td id="a44"><a href="UserService.java.html#L34" class="el_method">static {...}</a></td><td class="bar" id="b50"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c50">0%</td><td class="bar" id="d50"/><td class="ctr2" id="e50">n/a</td><td class="ctr1" id="f50">1</td><td class="ctr2" id="g50">1</td><td class="ctr1" id="h50">1</td><td class="ctr2" id="i50">1</td><td class="ctr1" id="j50">1</td><td class="ctr2" id="k50">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/UserService.java.html b/testResult/html/stirling.software.SPDF.config.security/UserService.java.html
    new file mode 100644
    index 000000000..103d31d5b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/UserService.java.html
    @@ -0,0 +1,481 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config.security</a> &gt; <span class="el_source">UserService.java</span></div><h1>UserService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config.security;
    +
    +import java.io.IOException;
    +import java.sql.SQLException;
    +import java.util.*;
    +
    +import org.springframework.context.MessageSource;
    +import org.springframework.context.i18n.LocaleContextHolder;
    +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.core.GrantedAuthority;
    +import org.springframework.security.core.authority.SimpleGrantedAuthority;
    +import org.springframework.security.core.context.SecurityContextHolder;
    +import org.springframework.security.core.session.SessionInformation;
    +import org.springframework.security.core.userdetails.UserDetails;
    +import org.springframework.security.core.userdetails.UsernameNotFoundException;
    +import org.springframework.security.crypto.password.PasswordEncoder;
    +import org.springframework.security.oauth2.core.user.OAuth2User;
    +import org.springframework.stereotype.Service;
    +import org.springframework.transaction.annotation.Transactional;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.interfaces.DatabaseInterface;
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
    +import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
    +import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
    +import stirling.software.SPDF.model.*;
    +import stirling.software.SPDF.model.exception.UnsupportedProviderException;
    +import stirling.software.SPDF.repository.AuthorityRepository;
    +import stirling.software.SPDF.repository.UserRepository;
    +
    +@Service
    +<span class="nc" id="L34">@Slf4j</span>
    +public class UserService implements UserServiceInterface {
    +
    +    private final UserRepository userRepository;
    +
    +    private final AuthorityRepository authorityRepository;
    +
    +    private final PasswordEncoder passwordEncoder;
    +
    +    private final MessageSource messageSource;
    +
    +    private final SessionPersistentRegistry sessionRegistry;
    +
    +    private final DatabaseInterface databaseService;
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    public UserService(
    +            UserRepository userRepository,
    +            AuthorityRepository authorityRepository,
    +            PasswordEncoder passwordEncoder,
    +            MessageSource messageSource,
    +            SessionPersistentRegistry sessionRegistry,
    +            DatabaseInterface databaseService,
    +<span class="nc" id="L58">            ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L59">        this.userRepository = userRepository;</span>
    +<span class="nc" id="L60">        this.authorityRepository = authorityRepository;</span>
    +<span class="nc" id="L61">        this.passwordEncoder = passwordEncoder;</span>
    +<span class="nc" id="L62">        this.messageSource = messageSource;</span>
    +<span class="nc" id="L63">        this.sessionRegistry = sessionRegistry;</span>
    +<span class="nc" id="L64">        this.databaseService = databaseService;</span>
    +<span class="nc" id="L65">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L66">    }</span>
    +
    +    @Transactional
    +    public void migrateOauth2ToSSO() {
    +<span class="nc" id="L70">        userRepository</span>
    +<span class="nc" id="L71">                .findByAuthenticationTypeIgnoreCase(&quot;OAUTH2&quot;)</span>
    +<span class="nc" id="L72">                .forEach(</span>
    +                        user -&gt; {
    +<span class="nc" id="L74">                            user.setAuthenticationType(AuthenticationType.SSO);</span>
    +<span class="nc" id="L75">                            userRepository.save(user);</span>
    +<span class="nc" id="L76">                        });</span>
    +<span class="nc" id="L77">    }</span>
    +
    +    // Handle OAUTH2 login and user auto creation.
    +    public void processSSOPostLogin(String username, boolean autoCreateUser)
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L82" title="All 2 branches missed.">        if (!isUsernameValid(username)) {</span>
    +<span class="nc" id="L83">            return;</span>
    +        }
    +<span class="nc" id="L85">        Optional&lt;User&gt; existingUser = findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">        if (existingUser.isPresent()) {</span>
    +<span class="nc" id="L87">            return;</span>
    +        }
    +<span class="nc bnc" id="L89" title="All 2 branches missed.">        if (autoCreateUser) {</span>
    +<span class="nc" id="L90">            saveUser(username, AuthenticationType.SSO);</span>
    +        }
    +<span class="nc" id="L92">    }</span>
    +
    +    public Authentication getAuthentication(String apiKey) {
    +<span class="nc" id="L95">        Optional&lt;User&gt; user = getUserByApiKey(apiKey);</span>
    +<span class="nc bnc" id="L96" title="All 2 branches missed.">        if (!user.isPresent()) {</span>
    +<span class="nc" id="L97">            throw new UsernameNotFoundException(&quot;API key is not valid&quot;);</span>
    +        }
    +        // Convert the user into an Authentication object
    +<span class="nc" id="L100">        return new UsernamePasswordAuthenticationToken( // principal (typically the user)</span>
    +                user, // credentials (we don't expose the password or API key here)
    +                null, // user's authorities (roles/permissions)
    +<span class="nc" id="L103">                getAuthorities(user.get()));</span>
    +    }
    +
    +    private Collection&lt;? extends GrantedAuthority&gt; getAuthorities(User user) {
    +        // Convert each Authority object into a SimpleGrantedAuthority object.
    +<span class="nc" id="L108">        return user.getAuthorities().stream()</span>
    +<span class="nc" id="L109">                .map((Authority authority) -&gt; new SimpleGrantedAuthority(authority.getAuthority()))</span>
    +<span class="nc" id="L110">                .toList();</span>
    +    }
    +
    +    private String generateApiKey() {
    +        String apiKey;
    +        do {
    +<span class="nc" id="L116">            apiKey = UUID.randomUUID().toString();</span>
    +<span class="nc" id="L117">        } while ( // Ensure uniqueness</span>
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">        userRepository.findByApiKey(apiKey).isPresent());</span>
    +<span class="nc" id="L119">        return apiKey;</span>
    +    }
    +
    +    public User addApiKeyToUser(String username) {
    +<span class="nc" id="L123">        Optional&lt;User&gt; userOpt = findByUsernameIgnoreCase(username);</span>
    +<span class="nc" id="L124">        User user = saveUser(userOpt, generateApiKey());</span>
    +        try {
    +<span class="nc" id="L126">            databaseService.exportDatabase();</span>
    +<span class="nc" id="L127">        } catch (SQLException | UnsupportedProviderException e) {</span>
    +<span class="nc" id="L128">            log.error(&quot;Error exporting database after adding API key to user&quot;, e);</span>
    +<span class="nc" id="L129">        }</span>
    +<span class="nc" id="L130">        return user;</span>
    +    }
    +
    +    public User refreshApiKeyForUser(String username) {
    +        // reuse the add API key method for refreshing
    +<span class="nc" id="L135">        return addApiKeyToUser(username);</span>
    +    }
    +
    +    public String getApiKeyForUser(String username) {
    +<span class="nc" id="L139">        User user =</span>
    +<span class="nc" id="L140">                findByUsernameIgnoreCase(username)</span>
    +<span class="nc" id="L141">                        .orElseThrow(() -&gt; new UsernameNotFoundException(&quot;User not found&quot;));</span>
    +<span class="nc bnc" id="L142" title="All 4 branches missed.">        if (user.getApiKey() == null || user.getApiKey().length() == 0) {</span>
    +<span class="nc" id="L143">            user = addApiKeyToUser(username);</span>
    +        }
    +<span class="nc" id="L145">        return user.getApiKey();</span>
    +    }
    +
    +    public boolean isValidApiKey(String apiKey) {
    +<span class="nc" id="L149">        return userRepository.findByApiKey(apiKey).isPresent();</span>
    +    }
    +
    +    public Optional&lt;User&gt; getUserByApiKey(String apiKey) {
    +<span class="nc" id="L153">        return userRepository.findByApiKey(apiKey);</span>
    +    }
    +
    +    public Optional&lt;User&gt; loadUserByApiKey(String apiKey) {
    +<span class="nc" id="L157">        Optional&lt;User&gt; user = userRepository.findByApiKey(apiKey);</span>
    +<span class="nc bnc" id="L158" title="All 2 branches missed.">        if (user.isPresent()) {</span>
    +<span class="nc" id="L159">            return user;</span>
    +        }
    +        // or throw an exception
    +<span class="nc" id="L162">        return null;</span>
    +    }
    +
    +    public boolean validateApiKeyForUser(String username, String apiKey) {
    +<span class="nc" id="L166">        Optional&lt;User&gt; userOpt = findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L167" title="All 4 branches missed.">        return userOpt.isPresent() &amp;&amp; apiKey.equals(userOpt.get().getApiKey());</span>
    +    }
    +
    +    public void saveUser(String username, AuthenticationType authenticationType)
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc" id="L172">        saveUser(username, authenticationType, Role.USER.getRoleId());</span>
    +<span class="nc" id="L173">    }</span>
    +
    +    private User saveUser(Optional&lt;User&gt; user, String apiKey) {
    +<span class="nc bnc" id="L176" title="All 2 branches missed.">        if (user.isPresent()) {</span>
    +<span class="nc" id="L177">            user.get().setApiKey(apiKey);</span>
    +<span class="nc" id="L178">            return userRepository.save(user.get());</span>
    +        }
    +<span class="nc" id="L180">        throw new UsernameNotFoundException(&quot;User not found&quot;);</span>
    +    }
    +
    +    public void saveUser(String username, AuthenticationType authenticationType, String role)
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L185" title="All 2 branches missed.">        if (!isUsernameValid(username)) {</span>
    +<span class="nc" id="L186">            throw new IllegalArgumentException(getInvalidUsernameMessage());</span>
    +        }
    +<span class="nc" id="L188">        User user = new User();</span>
    +<span class="nc" id="L189">        user.setUsername(username);</span>
    +<span class="nc" id="L190">        user.setEnabled(true);</span>
    +<span class="nc" id="L191">        user.setFirstLogin(false);</span>
    +<span class="nc" id="L192">        user.addAuthority(new Authority(role, user));</span>
    +<span class="nc" id="L193">        user.setAuthenticationType(authenticationType);</span>
    +<span class="nc" id="L194">        userRepository.save(user);</span>
    +<span class="nc" id="L195">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L196">    }</span>
    +
    +    public void saveUser(String username, String password)
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L200" title="All 2 branches missed.">        if (!isUsernameValid(username)) {</span>
    +<span class="nc" id="L201">            throw new IllegalArgumentException(getInvalidUsernameMessage());</span>
    +        }
    +<span class="nc" id="L203">        User user = new User();</span>
    +<span class="nc" id="L204">        user.setUsername(username);</span>
    +<span class="nc" id="L205">        user.setPassword(passwordEncoder.encode(password));</span>
    +<span class="nc" id="L206">        user.setEnabled(true);</span>
    +<span class="nc" id="L207">        user.setAuthenticationType(AuthenticationType.WEB);</span>
    +<span class="nc" id="L208">        user.addAuthority(new Authority(Role.USER.getRoleId(), user));</span>
    +<span class="nc" id="L209">        userRepository.save(user);</span>
    +<span class="nc" id="L210">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L211">    }</span>
    +
    +    public void saveUser(String username, String password, String role, boolean firstLogin)
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L215" title="All 2 branches missed.">        if (!isUsernameValid(username)) {</span>
    +<span class="nc" id="L216">            throw new IllegalArgumentException(getInvalidUsernameMessage());</span>
    +        }
    +<span class="nc" id="L218">        User user = new User();</span>
    +<span class="nc" id="L219">        user.setUsername(username);</span>
    +<span class="nc" id="L220">        user.setPassword(passwordEncoder.encode(password));</span>
    +<span class="nc" id="L221">        user.addAuthority(new Authority(role, user));</span>
    +<span class="nc" id="L222">        user.setEnabled(true);</span>
    +<span class="nc" id="L223">        user.setAuthenticationType(AuthenticationType.WEB);</span>
    +<span class="nc" id="L224">        user.setFirstLogin(firstLogin);</span>
    +<span class="nc" id="L225">        userRepository.save(user);</span>
    +<span class="nc" id="L226">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L227">    }</span>
    +
    +    public void saveUser(String username, String password, String role)
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc" id="L231">        saveUser(username, password, role, false);</span>
    +<span class="nc" id="L232">    }</span>
    +
    +    public void saveUser(String username, String password, boolean firstLogin, boolean enabled)
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L236" title="All 2 branches missed.">        if (!isUsernameValid(username)) {</span>
    +<span class="nc" id="L237">            throw new IllegalArgumentException(getInvalidUsernameMessage());</span>
    +        }
    +<span class="nc" id="L239">        User user = new User();</span>
    +<span class="nc" id="L240">        user.setUsername(username);</span>
    +<span class="nc" id="L241">        user.setPassword(passwordEncoder.encode(password));</span>
    +<span class="nc" id="L242">        user.addAuthority(new Authority(Role.USER.getRoleId(), user));</span>
    +<span class="nc" id="L243">        user.setEnabled(enabled);</span>
    +<span class="nc" id="L244">        user.setAuthenticationType(AuthenticationType.WEB);</span>
    +<span class="nc" id="L245">        user.setFirstLogin(firstLogin);</span>
    +<span class="nc" id="L246">        userRepository.save(user);</span>
    +<span class="nc" id="L247">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L248">    }</span>
    +
    +    public void deleteUser(String username) {
    +<span class="nc" id="L251">        Optional&lt;User&gt; userOpt = findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L252" title="All 2 branches missed.">        if (userOpt.isPresent()) {</span>
    +<span class="nc bnc" id="L253" title="All 2 branches missed.">            for (Authority authority : userOpt.get().getAuthorities()) {</span>
    +<span class="nc bnc" id="L254" title="All 2 branches missed.">                if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {</span>
    +<span class="nc" id="L255">                    return;</span>
    +                }
    +<span class="nc" id="L257">            }</span>
    +<span class="nc" id="L258">            userRepository.delete(userOpt.get());</span>
    +        }
    +<span class="nc" id="L260">        invalidateUserSessions(username);</span>
    +<span class="nc" id="L261">    }</span>
    +
    +    public boolean usernameExists(String username) {
    +<span class="nc" id="L264">        return findByUsername(username).isPresent();</span>
    +    }
    +
    +    public boolean usernameExistsIgnoreCase(String username) {
    +<span class="nc" id="L268">        return findByUsernameIgnoreCase(username).isPresent();</span>
    +    }
    +
    +    public boolean hasUsers() {
    +<span class="nc" id="L272">        long userCount = userRepository.count();</span>
    +<span class="nc bnc" id="L273" title="All 2 branches missed.">        if (findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId()).isPresent()) {</span>
    +<span class="nc" id="L274">            userCount -= 1;</span>
    +        }
    +<span class="nc bnc" id="L276" title="All 2 branches missed.">        return userCount &gt; 0;</span>
    +    }
    +
    +    public void updateUserSettings(String username, Map&lt;String, String&gt; updates)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L281">        Optional&lt;User&gt; userOpt = findByUsernameIgnoreCaseWithSettings(username);</span>
    +<span class="nc bnc" id="L282" title="All 2 branches missed.">        if (userOpt.isPresent()) {</span>
    +<span class="nc" id="L283">            User user = userOpt.get();</span>
    +<span class="nc" id="L284">            Map&lt;String, String&gt; settingsMap = user.getSettings();</span>
    +<span class="nc bnc" id="L285" title="All 2 branches missed.">            if (settingsMap == null) {</span>
    +<span class="nc" id="L286">                settingsMap = new HashMap&lt;&gt;();</span>
    +            }
    +<span class="nc" id="L288">            settingsMap.clear();</span>
    +<span class="nc" id="L289">            settingsMap.putAll(updates);</span>
    +<span class="nc" id="L290">            user.setSettings(settingsMap);</span>
    +<span class="nc" id="L291">            userRepository.save(user);</span>
    +<span class="nc" id="L292">            databaseService.exportDatabase();</span>
    +        }
    +<span class="nc" id="L294">    }</span>
    +
    +    public Optional&lt;User&gt; findByUsername(String username) {
    +<span class="nc" id="L297">        return userRepository.findByUsername(username);</span>
    +    }
    +
    +    public Optional&lt;User&gt; findByUsernameIgnoreCase(String username) {
    +<span class="nc" id="L301">        return userRepository.findByUsernameIgnoreCase(username);</span>
    +    }
    +
    +    public Optional&lt;User&gt; findByUsernameIgnoreCaseWithSettings(String username) {
    +<span class="nc" id="L305">        return userRepository.findByUsernameIgnoreCaseWithSettings(username);</span>
    +    }
    +
    +    public Authority findRole(User user) {
    +<span class="nc" id="L309">        return authorityRepository.findByUserId(user.getId());</span>
    +    }
    +
    +    public void changeUsername(User user, String newUsername)
    +            throws IllegalArgumentException,
    +                    IOException,
    +                    SQLException,
    +                    UnsupportedProviderException {
    +<span class="nc bnc" id="L317" title="All 2 branches missed.">        if (!isUsernameValid(newUsername)) {</span>
    +<span class="nc" id="L318">            throw new IllegalArgumentException(getInvalidUsernameMessage());</span>
    +        }
    +<span class="nc" id="L320">        user.setUsername(newUsername);</span>
    +<span class="nc" id="L321">        userRepository.save(user);</span>
    +<span class="nc" id="L322">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L323">    }</span>
    +
    +    public void changePassword(User user, String newPassword)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L327">        user.setPassword(passwordEncoder.encode(newPassword));</span>
    +<span class="nc" id="L328">        userRepository.save(user);</span>
    +<span class="nc" id="L329">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L330">    }</span>
    +
    +    public void changeFirstUse(User user, boolean firstUse)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L334">        user.setFirstLogin(firstUse);</span>
    +<span class="nc" id="L335">        userRepository.save(user);</span>
    +<span class="nc" id="L336">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L337">    }</span>
    +
    +    public void changeRole(User user, String newRole)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L341">        Authority userAuthority = this.findRole(user);</span>
    +<span class="nc" id="L342">        userAuthority.setAuthority(newRole);</span>
    +<span class="nc" id="L343">        authorityRepository.save(userAuthority);</span>
    +<span class="nc" id="L344">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L345">    }</span>
    +
    +    public void changeUserEnabled(User user, Boolean enbeled)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L349">        user.setEnabled(enbeled);</span>
    +<span class="nc" id="L350">        userRepository.save(user);</span>
    +<span class="nc" id="L351">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L352">    }</span>
    +
    +    public boolean isPasswordCorrect(User user, String currentPassword) {
    +<span class="nc" id="L355">        return passwordEncoder.matches(currentPassword, user.getPassword());</span>
    +    }
    +
    +    public boolean isUsernameValid(String username) {
    +        // Checks whether the simple username is formatted correctly
    +        // Regular expression for user name: Min. 3 characters, max. 50 characters
    +<span class="nc" id="L361">        boolean isValidSimpleUsername =</span>
    +<span class="nc" id="L362">                username.matches(&quot;^[a-zA-Z0-9](?!.*[-@._+]{2,})[a-zA-Z0-9@._+-]{1,48}[a-zA-Z0-9]$&quot;);</span>
    +
    +        // Checks whether the email address is formatted correctly
    +        // Regular expression for email addresses: Max. 320 characters, with RFC-like validation
    +<span class="nc" id="L366">        boolean isValidEmail =</span>
    +<span class="nc" id="L367">                username.matches(</span>
    +                        &quot;^(?=.{1,320}$)(?=.{1,64}@)[A-Za-z0-9](?:[A-Za-z0-9_.+-]*[A-Za-z0-9])?@[^-][A-Za-z0-9-]+(?:\\\\.[A-Za-z0-9-]+)*(?:\\\\.[A-Za-z]{2,})$&quot;);
    +
    +<span class="nc" id="L370">        List&lt;String&gt; notAllowedUserList = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L371">        notAllowedUserList.add(&quot;ALL_USERS&quot;.toLowerCase());</span>
    +<span class="nc" id="L372">        notAllowedUserList.add(&quot;anonymoususer&quot;);</span>
    +<span class="nc" id="L373">        boolean notAllowedUser = notAllowedUserList.contains(username.toLowerCase());</span>
    +<span class="nc bnc" id="L374" title="All 6 branches missed.">        return (isValidSimpleUsername || isValidEmail) &amp;&amp; !notAllowedUser;</span>
    +    }
    +
    +    private String getInvalidUsernameMessage() {
    +<span class="nc" id="L378">        return messageSource.getMessage(</span>
    +<span class="nc" id="L379">                &quot;invalidUsernameMessage&quot;, null, LocaleContextHolder.getLocale());</span>
    +    }
    +
    +    public boolean hasPassword(String username) {
    +<span class="nc" id="L383">        Optional&lt;User&gt; user = findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L384" title="All 4 branches missed.">        return user.isPresent() &amp;&amp; user.get().hasPassword();</span>
    +    }
    +
    +    public boolean isAuthenticationTypeByUsername(
    +            String username, AuthenticationType authenticationType) {
    +<span class="nc" id="L389">        Optional&lt;User&gt; user = findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L390" title="All 2 branches missed.">        return user.isPresent()</span>
    +<span class="nc bnc" id="L391" title="All 2 branches missed.">                &amp;&amp; authenticationType.name().equalsIgnoreCase(user.get().getAuthenticationType());</span>
    +    }
    +
    +    public boolean isUserDisabled(String username) {
    +<span class="nc" id="L395">        Optional&lt;User&gt; userOpt = findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L396" title="All 2 branches missed.">        return userOpt.map(user -&gt; !user.isEnabled()).orElse(false);</span>
    +    }
    +
    +    public void invalidateUserSessions(String username) {
    +<span class="nc" id="L400">        String usernameP = &quot;&quot;;</span>
    +
    +<span class="nc bnc" id="L402" title="All 2 branches missed.">        for (Object principal : sessionRegistry.getAllPrincipals()) {</span>
    +            for (SessionInformation sessionsInformation :
    +<span class="nc bnc" id="L404" title="All 2 branches missed.">                    sessionRegistry.getAllSessions(principal, false)) {</span>
    +<span class="nc bnc" id="L405" title="All 2 branches missed.">                if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L406">                    usernameP = detailsUser.getUsername();</span>
    +<span class="nc bnc" id="L407" title="All 2 branches missed.">                } else if (principal instanceof OAuth2User oAuth2User) {</span>
    +<span class="nc" id="L408">                    usernameP = oAuth2User.getName();</span>
    +<span class="nc bnc" id="L409" title="All 2 branches missed.">                } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {</span>
    +<span class="nc" id="L410">                    usernameP = saml2User.name();</span>
    +<span class="nc bnc" id="L411" title="All 2 branches missed.">                } else if (principal instanceof String stringUser) {</span>
    +<span class="nc" id="L412">                    usernameP = stringUser;</span>
    +                }
    +<span class="nc bnc" id="L414" title="All 2 branches missed.">                if (usernameP.equalsIgnoreCase(username)) {</span>
    +<span class="nc" id="L415">                    sessionRegistry.expireSession(sessionsInformation.getSessionId());</span>
    +                }
    +<span class="nc" id="L417">            }</span>
    +<span class="nc" id="L418">        }</span>
    +<span class="nc" id="L419">    }</span>
    +
    +    public String getCurrentUsername() {
    +<span class="nc" id="L422">        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();</span>
    +
    +<span class="nc bnc" id="L424" title="All 2 branches missed.">        if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L425">            return detailsUser.getUsername();</span>
    +<span class="nc bnc" id="L426" title="All 2 branches missed.">        } else if (principal instanceof OAuth2User oAuth2User) {</span>
    +<span class="nc" id="L427">            return oAuth2User.getAttribute(</span>
    +<span class="nc" id="L428">                    applicationProperties.getSecurity().getOauth2().getUseAsUsername());</span>
    +<span class="nc bnc" id="L429" title="All 2 branches missed.">        } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {</span>
    +<span class="nc" id="L430">            return saml2User.name();</span>
    +<span class="nc bnc" id="L431" title="All 2 branches missed.">        } else if (principal instanceof String stringUser) {</span>
    +<span class="nc" id="L432">            return stringUser;</span>
    +        }
    +<span class="nc" id="L434">        return null;</span>
    +    }
    +
    +    @Transactional
    +    public void syncCustomApiUser(String customApiKey) {
    +<span class="nc bnc" id="L439" title="All 4 branches missed.">        if (customApiKey == null || customApiKey.trim().isBlank()) {</span>
    +<span class="nc" id="L440">            return;</span>
    +        }
    +
    +<span class="nc" id="L443">        String username = &quot;CUSTOM_API_USER&quot;;</span>
    +<span class="nc" id="L444">        Optional&lt;User&gt; existingUser = findByUsernameIgnoreCase(username);</span>
    +
    +<span class="nc" id="L446">        existingUser.ifPresentOrElse(</span>
    +                user -&gt; {
    +                    // Update API key if it has changed
    +<span class="nc" id="L449">                    User updatedUser = existingUser.get();</span>
    +
    +<span class="nc bnc" id="L451" title="All 2 branches missed.">                    if (!customApiKey.equals(updatedUser.getApiKey())) {</span>
    +<span class="nc" id="L452">                        updatedUser.setApiKey(customApiKey);</span>
    +<span class="nc" id="L453">                        userRepository.save(updatedUser);</span>
    +                    }
    +<span class="nc" id="L455">                },</span>
    +                () -&gt; {
    +                    // Create new user with API role
    +<span class="nc" id="L458">                    User user = new User();</span>
    +<span class="nc" id="L459">                    user.setUsername(username);</span>
    +<span class="nc" id="L460">                    user.setPassword(UUID.randomUUID().toString());</span>
    +<span class="nc" id="L461">                    user.setEnabled(true);</span>
    +<span class="nc" id="L462">                    user.setFirstLogin(false);</span>
    +<span class="nc" id="L463">                    user.setAuthenticationType(AuthenticationType.WEB);</span>
    +<span class="nc" id="L464">                    user.setApiKey(customApiKey);</span>
    +<span class="nc" id="L465">                    user.addAuthority(new Authority(Role.INTERNAL_API_USER.getRoleId(), user));</span>
    +<span class="nc" id="L466">                    userRepository.save(user);</span>
    +<span class="nc" id="L467">                });</span>
    +
    +        try {
    +<span class="nc" id="L470">            databaseService.exportDatabase();</span>
    +<span class="nc" id="L471">        } catch (SQLException | UnsupportedProviderException e) {</span>
    +<span class="nc" id="L472">            log.error(&quot;Error exporting database after synchronising custom API user&quot;, e);</span>
    +<span class="nc" id="L473">        }</span>
    +<span class="nc" id="L474">    }</span>
    +
    +    @Override
    +    public long getTotalUsersCount() {
    +<span class="nc" id="L478">        return userRepository.count();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/index.html b/testResult/html/stirling.software.SPDF.config.security/index.html
    new file mode 100644
    index 000000000..1af9e1f8e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security</span></div><h1>stirling.software.SPDF.config.security</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">3,414 of 3,414</td><td class="ctr2">0%</td><td class="bar">407 of 407</td><td class="ctr2">0%</td><td class="ctr1">335</td><td class="ctr2">335</td><td class="ctr1">866</td><td class="ctr2">866</td><td class="ctr1">131</td><td class="ctr2">131</td><td class="ctr1">15</td><td class="ctr2">15</td></tr></tfoot><tbody><tr><td id="a14"><a href="UserService.html" class="el_class">UserService</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="953" alt="953"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="90" alt="90"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">96</td><td class="ctr2" id="g0">96</td><td class="ctr1" id="h0">239</td><td class="ctr2" id="i0">239</td><td class="ctr1" id="j0">51</td><td class="ctr2" id="k0">51</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a10"><a href="SecurityConfiguration.html" class="el_class">SecurityConfiguration</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="544" alt="544"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="48" alt="48"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">46</td><td class="ctr2" id="g1">46</td><td class="ctr1" id="h1">132</td><td class="ctr2" id="i1">132</td><td class="ctr1" id="j1">22</td><td class="ctr2" id="k1">22</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a11"><a href="UserAuthenticationFilter.html" class="el_class">UserAuthenticationFilter</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="428" alt="428"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="72" alt="72"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">41</td><td class="ctr2" id="g2">41</td><td class="ctr1" id="h2">103</td><td class="ctr2" id="i2">103</td><td class="ctr1" id="j6">5</td><td class="ctr2" id="k6">5</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="CustomLogoutSuccessHandler.html" class="el_class">CustomLogoutSuccessHandler</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="340" alt="340"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="52" height="10" title="39" alt="39"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">27</td><td class="ctr2" id="g3">27</td><td class="ctr1" id="h3">85</td><td class="ctr2" id="i3">85</td><td class="ctr1" id="j2">7</td><td class="ctr2" id="k2">7</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a13"><a href="UserBasedRateLimitingFilter.html" class="el_class">UserBasedRateLimitingFilter</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="197" alt="197"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="24" alt="24"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f5">18</td><td class="ctr2" id="g5">18</td><td class="ctr1" id="h4">53</td><td class="ctr2" id="i4">53</td><td class="ctr1" id="j4">6</td><td class="ctr2" id="k4">6</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a8"><a href="LoginAttemptService.html" class="el_class">LoginAttemptService</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="161" alt="161"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="36" alt="36"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">25</td><td class="ctr2" id="g4">25</td><td class="ctr1" id="h6">40</td><td class="ctr2" id="i6">40</td><td class="ctr1" id="j3">7</td><td class="ctr2" id="k3">7</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a1"><a href="CustomAuthenticationFailureHandler.html" class="el_class">CustomAuthenticationFailureHandler</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="144" alt="144"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="26" alt="26"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">18</td><td class="ctr2" id="g6">18</td><td class="ctr1" id="h7">36</td><td class="ctr2" id="i7">36</td><td class="ctr1" id="j7">5</td><td class="ctr2" id="k7">5</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a6"><a href="InitialSecuritySetup.html" class="el_class">InitialSecuritySetup</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="144" alt="144"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="18" alt="18"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">15</td><td class="ctr2" id="g7">15</td><td class="ctr1" id="h5">44</td><td class="ctr2" id="i5">44</td><td class="ctr1" id="j5">6</td><td class="ctr2" id="k5">6</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a5"><a href="FirstLoginFilter.html" class="el_class">FirstLoginFilter</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="134" alt="134"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="16" alt="16"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">11</td><td class="ctr2" id="g8">11</td><td class="ctr1" id="h8">33</td><td class="ctr2" id="i8">33</td><td class="ctr1" id="j10">3</td><td class="ctr2" id="k10">3</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a7"><a href="IPRateLimitingFilter.html" class="el_class">IPRateLimitingFilter</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="103" alt="103"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="10" alt="10"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f10">9</td><td class="ctr2" id="g10">9</td><td class="ctr1" id="h9">29</td><td class="ctr2" id="i9">29</td><td class="ctr1" id="j9">4</td><td class="ctr2" id="k9">4</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a4"><a href="CustomUserDetailsService.html" class="el_class">CustomUserDetailsService</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="71" alt="71"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f11">7</td><td class="ctr2" id="g11">7</td><td class="ctr1" id="h10">20</td><td class="ctr2" id="i10">20</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a0"><a href="AppUpdateAuthService.html" class="el_class">AppUpdateAuthService</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="69" alt="69"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="16" alt="16"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f9">10</td><td class="ctr2" id="g9">10</td><td class="ctr1" id="h12">17</td><td class="ctr2" id="i12">17</td><td class="ctr1" id="j13">2</td><td class="ctr2" id="k13">2</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a2"><a href="CustomAuthenticationSuccessHandler.html" class="el_class">CustomAuthenticationSuccessHandler</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="67" alt="67"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="8" alt="8"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">7</td><td class="ctr2" id="g12">7</td><td class="ctr1" id="h11">20</td><td class="ctr2" id="i11">20</td><td class="ctr1" id="j11">3</td><td class="ctr2" id="k11">3</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a12"><a href="UserAuthenticationFilter$LoginMethod.html" class="el_class">UserAuthenticationFilter.LoginMethod</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="49" alt="49"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">3</td><td class="ctr2" id="g13">3</td><td class="ctr1" id="h13">10</td><td class="ctr2" id="i13">10</td><td class="ctr1" id="j12">3</td><td class="ctr2" id="k12">3</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a9"><a href="RateLimitResetScheduler.html" class="el_class">RateLimitResetScheduler</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="10" alt="10"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">2</td><td class="ctr2" id="g14">2</td><td class="ctr1" id="h14">5</td><td class="ctr2" id="i14">5</td><td class="ctr1" id="j14">2</td><td class="ctr2" id="k14">2</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m14">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config.security/index.source.html b/testResult/html/stirling.software.SPDF.config.security/index.source.html
    new file mode 100644
    index 000000000..8067e07ad
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config.security/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config.security</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config.security</span></div><h1>stirling.software.SPDF.config.security</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">3,414 of 3,414</td><td class="ctr2">0%</td><td class="bar">407 of 407</td><td class="ctr2">0%</td><td class="ctr1">335</td><td class="ctr2">335</td><td class="ctr1">866</td><td class="ctr2">866</td><td class="ctr1">131</td><td class="ctr2">131</td><td class="ctr1">15</td><td class="ctr2">15</td></tr></tfoot><tbody><tr><td id="a13"><a href="UserService.java.html" class="el_source">UserService.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="953" alt="953"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="90" alt="90"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">96</td><td class="ctr2" id="g0">96</td><td class="ctr1" id="h0">239</td><td class="ctr2" id="i0">239</td><td class="ctr1" id="j0">51</td><td class="ctr2" id="k0">51</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a10"><a href="SecurityConfiguration.java.html" class="el_source">SecurityConfiguration.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="544" alt="544"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="48" alt="48"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">46</td><td class="ctr2" id="g1">46</td><td class="ctr1" id="h1">132</td><td class="ctr2" id="i1">132</td><td class="ctr1" id="j1">22</td><td class="ctr2" id="k1">22</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a11"><a href="UserAuthenticationFilter.java.html" class="el_source">UserAuthenticationFilter.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="477" alt="477"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="72" alt="72"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">44</td><td class="ctr2" id="g2">44</td><td class="ctr1" id="h2">113</td><td class="ctr2" id="i2">113</td><td class="ctr1" id="j2">8</td><td class="ctr2" id="k2">8</td><td class="ctr1" id="l0">2</td><td class="ctr2" id="m0">2</td></tr><tr><td id="a3"><a href="CustomLogoutSuccessHandler.java.html" class="el_source">CustomLogoutSuccessHandler.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="340" alt="340"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="52" height="10" title="39" alt="39"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">27</td><td class="ctr2" id="g3">27</td><td class="ctr1" id="h3">85</td><td class="ctr2" id="i3">85</td><td class="ctr1" id="j3">7</td><td class="ctr2" id="k3">7</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a12"><a href="UserBasedRateLimitingFilter.java.html" class="el_source">UserBasedRateLimitingFilter.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="197" alt="197"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="24" alt="24"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f5">18</td><td class="ctr2" id="g5">18</td><td class="ctr1" id="h4">53</td><td class="ctr2" id="i4">53</td><td class="ctr1" id="j5">6</td><td class="ctr2" id="k5">6</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a8"><a href="LoginAttemptService.java.html" class="el_source">LoginAttemptService.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="161" alt="161"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="36" alt="36"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">25</td><td class="ctr2" id="g4">25</td><td class="ctr1" id="h6">40</td><td class="ctr2" id="i6">40</td><td class="ctr1" id="j4">7</td><td class="ctr2" id="k4">7</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a6"><a href="InitialSecuritySetup.java.html" class="el_source">InitialSecuritySetup.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="144" alt="144"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="18" alt="18"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f7">15</td><td class="ctr2" id="g7">15</td><td class="ctr1" id="h5">44</td><td class="ctr2" id="i5">44</td><td class="ctr1" id="j6">6</td><td class="ctr2" id="k6">6</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a1"><a href="CustomAuthenticationFailureHandler.java.html" class="el_source">CustomAuthenticationFailureHandler.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="144" alt="144"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="26" alt="26"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f6">18</td><td class="ctr2" id="g6">18</td><td class="ctr1" id="h7">36</td><td class="ctr2" id="i7">36</td><td class="ctr1" id="j7">5</td><td class="ctr2" id="k7">5</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a5"><a href="FirstLoginFilter.java.html" class="el_source">FirstLoginFilter.java</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="134" alt="134"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="16" alt="16"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">11</td><td class="ctr2" id="g8">11</td><td class="ctr1" id="h8">33</td><td class="ctr2" id="i8">33</td><td class="ctr1" id="j10">3</td><td class="ctr2" id="k10">3</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a7"><a href="IPRateLimitingFilter.java.html" class="el_source">IPRateLimitingFilter.java</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="103" alt="103"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="10" alt="10"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f10">9</td><td class="ctr2" id="g10">9</td><td class="ctr1" id="h9">29</td><td class="ctr2" id="i9">29</td><td class="ctr1" id="j9">4</td><td class="ctr2" id="k9">4</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a4"><a href="CustomUserDetailsService.java.html" class="el_source">CustomUserDetailsService.java</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="71" alt="71"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f11">7</td><td class="ctr2" id="g11">7</td><td class="ctr1" id="h10">20</td><td class="ctr2" id="i10">20</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a0"><a href="AppUpdateAuthService.java.html" class="el_source">AppUpdateAuthService.java</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="69" alt="69"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="16" alt="16"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f9">10</td><td class="ctr2" id="g9">10</td><td class="ctr1" id="h12">17</td><td class="ctr2" id="i12">17</td><td class="ctr1" id="j12">2</td><td class="ctr2" id="k12">2</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a2"><a href="CustomAuthenticationSuccessHandler.java.html" class="el_source">CustomAuthenticationSuccessHandler.java</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="67" alt="67"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="8" alt="8"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">7</td><td class="ctr2" id="g12">7</td><td class="ctr1" id="h11">20</td><td class="ctr2" id="i11">20</td><td class="ctr1" id="j11">3</td><td class="ctr2" id="k11">3</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a9"><a href="RateLimitResetScheduler.java.html" class="el_source">RateLimitResetScheduler.java</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="10" alt="10"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">2</td><td class="ctr2" id="g13">2</td><td class="ctr1" id="h13">5</td><td class="ctr2" id="i13">5</td><td class="ctr1" id="j13">2</td><td class="ctr2" id="k13">2</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m13">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/AppConfig.html b/testResult/html/stirling.software.SPDF.config/AppConfig.html
    new file mode 100644
    index 000000000..5b8136536
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/AppConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AppConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">AppConfig</span></div><h1>AppConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">251 of 251</td><td class="ctr2">0%</td><td class="bar">26 of 26</td><td class="ctr2">0%</td><td class="ctr1">40</td><td class="ctr2">40</td><td class="ctr1">57</td><td class="ctr2">57</td><td class="ctr1">27</td><td class="ctr2">27</td></tr></tfoot><tbody><tr><td id="a11"><a href="AppConfig.java.html#L117" class="el_method">isRunningInDockerWithConfig()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="32" alt="32"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">9</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="AppConfig.java.html#L59" class="el_method">appVersion()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="24" alt="24"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h1">7</td><td class="ctr2" id="i1">7</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a17"><a href="AppConfig.java.html#L90" class="el_method">navBarText()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="78" height="10" title="21" alt="21"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a20"><a href="AppConfig.java.html#L105" class="el_method">rateLimit()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="16" alt="16"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a13"><a href="AppConfig.java.html#L143" class="el_method">lambda$processOnlyFiles$1(Path)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="16" alt="16"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h4">3</td><td class="ctr2" id="i4">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a8"><a href="AppConfig.java.html#L98" class="el_method">enableAlphaFunctionality()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="13" alt="13"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a24"><a href="AppConfig.java.html#L41" class="el_method">templateEngine(ResourceLoader)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="12" alt="12"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a9"><a href="AppConfig.java.html#L72" class="el_method">homeText()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="12" alt="12"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a1"><a href="AppConfig.java.html#L185" class="el_method">analyticsEnabled()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="12" alt="12"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a4"><a href="AppConfig.java.html#L53" class="el_method">appName()</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="11" alt="11"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h10">2</td><td class="ctr2" id="i10">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a2"><a href="AppConfig.java.html#L179" class="el_method">analyticsPrompt()</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="9" alt="9"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h11">1</td><td class="ctr2" id="i11">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a21"><a href="AppConfig.java.html#L112" class="el_method">runningInDocker()</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="8" alt="8"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h12">1</td><td class="ctr2" id="i12">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a3"><a href="AppConfig.java.html#L34" class="el_method">AppConfig(ApplicationProperties)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="6" alt="6"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h8">3</td><td class="ctr2" id="i8">3</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a15"><a href="AppConfig.java.html#L48" class="el_method">loginEnabled()</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="6" alt="6"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">1</td><td class="ctr2" id="i13">1</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a14"><a href="AppConfig.java.html#L79" class="el_method">languages()</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="5" alt="5"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h14">1</td><td class="ctr2" id="i14">1</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a25"><a href="AppConfig.java.html#L153" class="el_method">termsAndConditions()</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="5" alt="5"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h15">1</td><td class="ctr2" id="i15">1</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a18"><a href="AppConfig.java.html#L158" class="el_method">privacyPolicy()</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="5" alt="5"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h16">1</td><td class="ctr2" id="i16">1</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a7"><a href="AppConfig.java.html#L163" class="el_method">cookiePolicy()</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="5" alt="5"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h17">1</td><td class="ctr2" id="i17">1</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a10"><a href="AppConfig.java.html#L168" class="el_method">impressum()</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="5" alt="5"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h18">1</td><td class="ctr2" id="i18">1</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a0"><a href="AppConfig.java.html#L173" class="el_method">accessibilityStatement()</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="5" alt="5"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h19">1</td><td class="ctr2" id="i19">1</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a26"><a href="AppConfig.java.html#L196" class="el_method">uuid()</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="5" alt="5"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">1</td><td class="ctr2" id="i20">1</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a23"><a href="AppConfig.java.html#L191" class="el_method">stirlingPDFLabel()</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="4" alt="4"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">1</td><td class="ctr2" id="i21">1</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a12"><a href="AppConfig.java.html#L128" class="el_method">lambda$isRunningInDockerWithConfig$0(String)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="4" alt="4"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h22">1</td><td class="ctr2" id="i22">1</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a22"><a href="AppConfig.java.html#L29" class="el_method">static {...}</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="4" alt="4"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">1</td><td class="ctr2" id="i23">1</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a6"><a href="AppConfig.java.html#L84" class="el_method">contextPath(String)</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="2" alt="2"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h24">1</td><td class="ctr2" id="i24">1</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr><tr><td id="a16"><a href="AppConfig.java.html#L137" class="el_method">missingActiveSecurity()</a></td><td class="bar" id="b25"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="2" alt="2"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g25">1</td><td class="ctr1" id="h25">1</td><td class="ctr2" id="i25">1</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k25">1</td></tr><tr><td id="a19"><a href="AppConfig.java.html#L142" class="el_method">processOnlyFiles()</a></td><td class="bar" id="b26"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="2" alt="2"/></td><td class="ctr2" id="c26">0%</td><td class="bar" id="d26"/><td class="ctr2" id="e26">n/a</td><td class="ctr1" id="f26">1</td><td class="ctr2" id="g26">1</td><td class="ctr1" id="h26">1</td><td class="ctr2" id="i26">1</td><td class="ctr1" id="j26">1</td><td class="ctr2" id="k26">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/AppConfig.java.html b/testResult/html/stirling.software.SPDF.config/AppConfig.java.html
    new file mode 100644
    index 000000000..73ad81488
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/AppConfig.java.html
    @@ -0,0 +1,199 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AppConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">AppConfig.java</span></div><h1>AppConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.util.List;
    +import java.util.Properties;
    +import java.util.function.Predicate;
    +
    +import org.springframework.beans.factory.annotation.Value;
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
    +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.context.annotation.Lazy;
    +import org.springframework.context.annotation.Scope;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.core.io.Resource;
    +import org.springframework.core.io.ResourceLoader;
    +import org.thymeleaf.spring6.SpringTemplateEngine;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +@Configuration
    +@Lazy
    +<span class="nc" id="L29">@Slf4j</span>
    +public class AppConfig {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L34">    public AppConfig(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L35">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L36">    }</span>
    +
    +    @Bean
    +    @ConditionalOnProperty(name = &quot;system.customHTMLFiles&quot;, havingValue = &quot;true&quot;)
    +    public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
    +<span class="nc" id="L41">        SpringTemplateEngine templateEngine = new SpringTemplateEngine();</span>
    +<span class="nc" id="L42">        templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));</span>
    +<span class="nc" id="L43">        return templateEngine;</span>
    +    }
    +
    +    @Bean(name = &quot;loginEnabled&quot;)
    +    public boolean loginEnabled() {
    +<span class="nc" id="L48">        return applicationProperties.getSecurity().getEnableLogin();</span>
    +    }
    +
    +    @Bean(name = &quot;appName&quot;)
    +    public String appName() {
    +<span class="nc" id="L53">        String homeTitle = applicationProperties.getUi().getAppName();</span>
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">        return (homeTitle != null) ? homeTitle : &quot;Stirling PDF&quot;;</span>
    +    }
    +
    +    @Bean(name = &quot;appVersion&quot;)
    +    public String appVersion() {
    +<span class="nc" id="L59">        Resource resource = new ClassPathResource(&quot;version.properties&quot;);</span>
    +<span class="nc" id="L60">        Properties props = new Properties();</span>
    +        try {
    +<span class="nc" id="L62">            props.load(resource.getInputStream());</span>
    +<span class="nc" id="L63">            return props.getProperty(&quot;version&quot;);</span>
    +<span class="nc" id="L64">        } catch (IOException e) {</span>
    +<span class="nc" id="L65">            log.error(&quot;exception&quot;, e);</span>
    +        }
    +<span class="nc" id="L67">        return &quot;0.0.0&quot;;</span>
    +    }
    +
    +    @Bean(name = &quot;homeText&quot;)
    +    public String homeText() {
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">        return (applicationProperties.getUi().getHomeDescription() != null)</span>
    +<span class="nc" id="L73">                ? applicationProperties.getUi().getHomeDescription()</span>
    +<span class="nc" id="L74">                : &quot;null&quot;;</span>
    +    }
    +
    +    @Bean(name = &quot;languages&quot;)
    +    public List&lt;String&gt; languages() {
    +<span class="nc" id="L79">        return applicationProperties.getUi().getLanguages();</span>
    +    }
    +
    +    @Bean
    +    public String contextPath(@Value(&quot;${server.servlet.context-path}&quot;) String contextPath) {
    +<span class="nc" id="L84">        return contextPath;</span>
    +    }
    +
    +    @Bean(name = &quot;navBarText&quot;)
    +    public String navBarText() {
    +        String defaultNavBar =
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">                applicationProperties.getUi().getAppNameNavbar() != null</span>
    +<span class="nc" id="L91">                        ? applicationProperties.getUi().getAppNameNavbar()</span>
    +<span class="nc" id="L92">                        : applicationProperties.getUi().getAppName();</span>
    +<span class="nc bnc" id="L93" title="All 2 branches missed.">        return (defaultNavBar != null) ? defaultNavBar : &quot;Stirling PDF&quot;;</span>
    +    }
    +
    +    @Bean(name = &quot;enableAlphaFunctionality&quot;)
    +    public boolean enableAlphaFunctionality() {
    +<span class="nc bnc" id="L98" title="All 2 branches missed.">        return applicationProperties.getSystem().getEnableAlphaFunctionality() != null</span>
    +<span class="nc" id="L99">                ? applicationProperties.getSystem().getEnableAlphaFunctionality()</span>
    +<span class="nc" id="L100">                : false;</span>
    +    }
    +
    +    @Bean(name = &quot;rateLimit&quot;)
    +    public boolean rateLimit() {
    +<span class="nc" id="L105">        String rateLimit = System.getProperty(&quot;rateLimit&quot;);</span>
    +<span class="nc bnc" id="L106" title="All 2 branches missed.">        if (rateLimit == null) rateLimit = System.getenv(&quot;rateLimit&quot;);</span>
    +<span class="nc bnc" id="L107" title="All 2 branches missed.">        return (rateLimit != null) ? Boolean.valueOf(rateLimit) : false;</span>
    +    }
    +
    +    @Bean(name = &quot;RunningInDocker&quot;)
    +    public boolean runningInDocker() {
    +<span class="nc" id="L112">        return Files.exists(Paths.get(&quot;/.dockerenv&quot;));</span>
    +    }
    +
    +    @Bean(name = &quot;configDirMounted&quot;)
    +    public boolean isRunningInDockerWithConfig() {
    +<span class="nc" id="L117">        Path dockerEnv = Paths.get(&quot;/.dockerenv&quot;);</span>
    +        // default to true if not docker
    +<span class="nc bnc" id="L119" title="All 2 branches missed.">        if (!Files.exists(dockerEnv)) {</span>
    +<span class="nc" id="L120">            return true;</span>
    +        }
    +<span class="nc" id="L122">        Path mountInfo = Paths.get(&quot;/proc/1/mountinfo&quot;);</span>
    +        // this should always exist, if not some unknown usecase
    +<span class="nc bnc" id="L124" title="All 2 branches missed.">        if (!Files.exists(mountInfo)) {</span>
    +<span class="nc" id="L125">            return true;</span>
    +        }
    +        try {
    +<span class="nc" id="L128">            return Files.lines(mountInfo).anyMatch(line -&gt; line.contains(&quot; /configs &quot;));</span>
    +<span class="nc" id="L129">        } catch (IOException e) {</span>
    +<span class="nc" id="L130">            return false;</span>
    +        }
    +    }
    +
    +    @ConditionalOnMissingClass(&quot;stirling.software.SPDF.config.security.SecurityConfiguration&quot;)
    +    @Bean(name = &quot;activeSecurity&quot;)
    +    public boolean missingActiveSecurity() {
    +<span class="nc" id="L137">        return false;</span>
    +    }
    +
    +    @Bean(name = &quot;directoryFilter&quot;)
    +    public Predicate&lt;Path&gt; processOnlyFiles() {
    +<span class="nc" id="L142">        return path -&gt; {</span>
    +<span class="nc bnc" id="L143" title="All 2 branches missed.">            if (Files.isDirectory(path)) {</span>
    +<span class="nc bnc" id="L144" title="All 2 branches missed.">                return !path.toString().contains(&quot;processing&quot;);</span>
    +            } else {
    +<span class="nc" id="L146">                return true;</span>
    +            }
    +        };
    +    }
    +
    +    @Bean(name = &quot;termsAndConditions&quot;)
    +    public String termsAndConditions() {
    +<span class="nc" id="L153">        return applicationProperties.getLegal().getTermsAndConditions();</span>
    +    }
    +
    +    @Bean(name = &quot;privacyPolicy&quot;)
    +    public String privacyPolicy() {
    +<span class="nc" id="L158">        return applicationProperties.getLegal().getPrivacyPolicy();</span>
    +    }
    +
    +    @Bean(name = &quot;cookiePolicy&quot;)
    +    public String cookiePolicy() {
    +<span class="nc" id="L163">        return applicationProperties.getLegal().getCookiePolicy();</span>
    +    }
    +
    +    @Bean(name = &quot;impressum&quot;)
    +    public String impressum() {
    +<span class="nc" id="L168">        return applicationProperties.getLegal().getImpressum();</span>
    +    }
    +
    +    @Bean(name = &quot;accessibilityStatement&quot;)
    +    public String accessibilityStatement() {
    +<span class="nc" id="L173">        return applicationProperties.getLegal().getAccessibilityStatement();</span>
    +    }
    +
    +    @Bean(name = &quot;analyticsPrompt&quot;)
    +    @Scope(&quot;request&quot;)
    +    public boolean analyticsPrompt() {
    +<span class="nc bnc" id="L179" title="All 2 branches missed.">        return applicationProperties.getSystem().getEnableAnalytics() == null;</span>
    +    }
    +
    +    @Bean(name = &quot;analyticsEnabled&quot;)
    +    @Scope(&quot;request&quot;)
    +    public boolean analyticsEnabled() {
    +<span class="nc bnc" id="L185" title="All 2 branches missed.">        if (applicationProperties.getPremium().isEnabled()) return true;</span>
    +<span class="nc" id="L186">        return applicationProperties.getSystem().isAnalyticsEnabled();</span>
    +    }
    +
    +    @Bean(name = &quot;StirlingPDFLabel&quot;)
    +    public String stirlingPDFLabel() {
    +<span class="nc" id="L191">        return &quot;Stirling-PDF&quot; + &quot; v&quot; + appVersion();</span>
    +    }
    +
    +    @Bean(name = &quot;UUID&quot;)
    +    public String uuid() {
    +<span class="nc" id="L196">        return applicationProperties.getAutomaticallyGenerated().getUUID();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/AppUpdateService.html b/testResult/html/stirling.software.SPDF.config/AppUpdateService.html
    new file mode 100644
    index 000000000..bc8fa45f5
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/AppUpdateService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AppUpdateService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">AppUpdateService</span></div><h1>AppUpdateService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">31 of 31</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="AppUpdateService.java.html#L28" class="el_method">shouldShow()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="AppUpdateService.java.html#L20" class="el_method">AppUpdateService(ApplicationProperties, ShowAdminInterface)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/AppUpdateService.java.html b/testResult/html/stirling.software.SPDF.config/AppUpdateService.java.html
    new file mode 100644
    index 000000000..fbfdafd0c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/AppUpdateService.java.html
    @@ -0,0 +1,33 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AppUpdateService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">AppUpdateService.java</span></div><h1>AppUpdateService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.context.annotation.Scope;
    +
    +import stirling.software.SPDF.config.interfaces.ShowAdminInterface;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +@Configuration
    +class AppUpdateService {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    private final ShowAdminInterface showAdmin;
    +
    +    public AppUpdateService(
    +            ApplicationProperties applicationProperties,
    +<span class="nc" id="L20">            @Autowired(required = false) ShowAdminInterface showAdmin) {</span>
    +<span class="nc" id="L21">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L22">        this.showAdmin = showAdmin;</span>
    +<span class="nc" id="L23">    }</span>
    +
    +    @Bean(name = &quot;shouldShow&quot;)
    +    @Scope(&quot;request&quot;)
    +    public boolean shouldShow() {
    +<span class="nc" id="L28">        boolean showUpdate = applicationProperties.getSystem().isShowUpdate();</span>
    +<span class="nc bnc" id="L29" title="All 2 branches missed.">        boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true;</span>
    +<span class="nc bnc" id="L30" title="All 4 branches missed.">        return showUpdate &amp;&amp; showAdminResult;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/CleanUrlInterceptor.html b/testResult/html/stirling.software.SPDF.config/CleanUrlInterceptor.html
    new file mode 100644
    index 000000000..5deeb2a77
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/CleanUrlInterceptor.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CleanUrlInterceptor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">CleanUrlInterceptor</span></div><h1>CleanUrlInterceptor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">160 of 160</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">27</td><td class="ctr2">27</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a3"><a href="CleanUrlInterceptor.java.html#L32" class="el_method">preHandle(HttpServletRequest, HttpServletResponse, Object)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="114" alt="114"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">22</td><td class="ctr2" id="i0">22</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a4"><a href="CleanUrlInterceptor.java.html#L16" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="41" alt="41"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="CleanUrlInterceptor.java.html#L14" class="el_method">CleanUrlInterceptor()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="CleanUrlInterceptor.java.html#L75" class="el_method">postHandle(HttpServletRequest, HttpServletResponse, Object, ModelAndView)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="1" alt="1"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="CleanUrlInterceptor.java.html#L82" class="el_method">afterCompletion(HttpServletRequest, HttpServletResponse, Object, Exception)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="1" alt="1"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/CleanUrlInterceptor.java.html b/testResult/html/stirling.software.SPDF.config/CleanUrlInterceptor.java.html
    new file mode 100644
    index 000000000..80a76d24b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/CleanUrlInterceptor.java.html
    @@ -0,0 +1,84 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CleanUrlInterceptor.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">CleanUrlInterceptor.java</span></div><h1>CleanUrlInterceptor.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.util.Arrays;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.springframework.web.servlet.HandlerInterceptor;
    +import org.springframework.web.servlet.ModelAndView;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +<span class="nc" id="L14">public class CleanUrlInterceptor implements HandlerInterceptor {</span>
    +
    +<span class="nc" id="L16">    private static final List&lt;String&gt; ALLOWED_PARAMS =</span>
    +<span class="nc" id="L17">            Arrays.asList(</span>
    +                    &quot;lang&quot;,
    +                    &quot;endpoint&quot;,
    +                    &quot;endpoints&quot;,
    +                    &quot;logout&quot;,
    +                    &quot;error&quot;,
    +                    &quot;errorOAuth&quot;,
    +                    &quot;file&quot;,
    +                    &quot;messageType&quot;,
    +                    &quot;infoMessage&quot;);
    +
    +    @Override
    +    public boolean preHandle(
    +            HttpServletRequest request, HttpServletResponse response, Object handler)
    +            throws Exception {
    +<span class="nc" id="L32">        String queryString = request.getQueryString();</span>
    +<span class="nc bnc" id="L33" title="All 4 branches missed.">        if (queryString != null &amp;&amp; !queryString.isEmpty()) {</span>
    +<span class="nc" id="L34">            String requestURI = request.getRequestURI();</span>
    +<span class="nc" id="L35">            Map&lt;String, String&gt; allowedParameters = new HashMap&lt;&gt;();</span>
    +
    +            // Keep only the allowed parameters
    +<span class="nc" id="L38">            String[] queryParameters = queryString.split(&quot;&amp;&quot;);</span>
    +<span class="nc bnc" id="L39" title="All 2 branches missed.">            for (String param : queryParameters) {</span>
    +<span class="nc" id="L40">                String[] keyValuePair = param.split(&quot;=&quot;);</span>
    +<span class="nc bnc" id="L41" title="All 2 branches missed.">                if (keyValuePair.length != 2) {</span>
    +<span class="nc" id="L42">                    continue;</span>
    +                }
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">                if (ALLOWED_PARAMS.contains(keyValuePair[0])) {</span>
    +<span class="nc" id="L45">                    allowedParameters.put(keyValuePair[0], keyValuePair[1]);</span>
    +                }
    +            }
    +
    +            // If there are any parameters that are not allowed
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">            if (allowedParameters.size() != queryParameters.length) {</span>
    +                // Construct new query string
    +<span class="nc" id="L52">                StringBuilder newQueryString = new StringBuilder();</span>
    +<span class="nc bnc" id="L53" title="All 2 branches missed.">                for (Map.Entry&lt;String, String&gt; entry : allowedParameters.entrySet()) {</span>
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">                    if (newQueryString.length() &gt; 0) {</span>
    +<span class="nc" id="L55">                        newQueryString.append(&quot;&amp;&quot;);</span>
    +                    }
    +<span class="nc" id="L57">                    newQueryString.append(entry.getKey()).append(&quot;=&quot;).append(entry.getValue());</span>
    +<span class="nc" id="L58">                }</span>
    +
    +                // Redirect to the URL with only allowed query parameters
    +<span class="nc" id="L61">                String redirectUrl = requestURI + &quot;?&quot; + newQueryString;</span>
    +
    +<span class="nc" id="L63">                response.sendRedirect(request.getContextPath() + redirectUrl);</span>
    +<span class="nc" id="L64">                return false;</span>
    +            }
    +        }
    +<span class="nc" id="L67">        return true;</span>
    +    }
    +
    +    @Override
    +    public void postHandle(
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            Object handler,
    +<span class="nc" id="L75">            ModelAndView modelAndView) {}</span>
    +
    +    @Override
    +    public void afterCompletion(
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            Object handler,
    +<span class="nc" id="L82">            Exception ex) {}</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/ConfigInitializer.html b/testResult/html/stirling.software.SPDF.config/ConfigInitializer.html
    new file mode 100644
    index 000000000..2b136b13c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/ConfigInitializer.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConfigInitializer</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">ConfigInitializer</span></div><h1>ConfigInitializer</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">417 of 417</td><td class="ctr2">0%</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="ctr1">16</td><td class="ctr2">16</td><td class="ctr1">65</td><td class="ctr2">65</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="ConfigInitializer.java.html#L85" class="el_method">migrateEnterpriseEditionToPremium(YamlHelper, YamlHelper)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="259" alt="259"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h1">29</td><td class="ctr2" id="i1">29</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ConfigInitializer.java.html#L26" class="el_method">ensureConfigExists()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="69" height="10" title="151" alt="151"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="85" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h0">34</td><td class="ctr2" id="i0">34</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="ConfigInitializer.java.html#L21" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="ConfigInitializer.java.html#L22" class="el_method">ConfigInitializer()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/ConfigInitializer.java.html b/testResult/html/stirling.software.SPDF.config/ConfigInitializer.java.html
    new file mode 100644
    index 000000000..efdbbd31c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/ConfigInitializer.java.html
    @@ -0,0 +1,125 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConfigInitializer.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">ConfigInitializer.java</span></div><h1>ConfigInitializer.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.FileNotFoundException;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.nio.file.StandardCopyOption;
    +import java.util.List;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +/**
    + * A naive, line-based approach to merging &quot;settings.yml&quot; with &quot;settings.yml.template&quot; while
    + * preserving exact whitespace, blank lines, and inline comments -- but we only rewrite the file if
    + * the merged content actually differs.
    + */
    +<span class="nc" id="L21">@Slf4j</span>
    +<span class="nc" id="L22">public class ConfigInitializer {</span>
    +
    +    public void ensureConfigExists() throws IOException, URISyntaxException {
    +        // 1) If settings file doesn't exist, create from template
    +<span class="nc" id="L26">        Path destPath = Paths.get(InstallationPathConfig.getSettingsPath());</span>
    +<span class="nc bnc" id="L27" title="All 2 branches missed.">        if (Files.notExists(destPath)) {</span>
    +<span class="nc" id="L28">            Files.createDirectories(destPath.getParent());</span>
    +<span class="nc" id="L29">            try (InputStream in =</span>
    +<span class="nc" id="L30">                    getClass().getClassLoader().getResourceAsStream(&quot;settings.yml.template&quot;)) {</span>
    +<span class="nc bnc" id="L31" title="All 2 branches missed.">                if (in == null) {</span>
    +<span class="nc" id="L32">                    throw new FileNotFoundException(</span>
    +                            &quot;Resource file not found: settings.yml.template&quot;);
    +                }
    +<span class="nc" id="L35">                Files.copy(in, destPath);</span>
    +            }
    +<span class="nc" id="L37">            log.info(&quot;Created settings file from template&quot;);</span>
    +        } else {
    +            // 2) Merge existing file with the template
    +<span class="nc" id="L40">            URL templateResource = getClass().getClassLoader().getResource(&quot;settings.yml.template&quot;);</span>
    +<span class="nc bnc" id="L41" title="All 2 branches missed.">            if (templateResource == null) {</span>
    +<span class="nc" id="L42">                throw new IOException(&quot;Resource not found: settings.yml.template&quot;);</span>
    +            }
    +
    +            // Copy template to a temp location so we can read lines
    +<span class="nc" id="L46">            Path tempTemplatePath = Files.createTempFile(&quot;settings.yml&quot;, &quot;.template&quot;);</span>
    +<span class="nc" id="L47">            try (InputStream in = templateResource.openStream()) {</span>
    +<span class="nc" id="L48">                Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);</span>
    +            }
    +
    +            // Copy setting.yaml to a temp location so we can read lines
    +<span class="nc" id="L52">            Path settingTempPath = Files.createTempFile(&quot;settings&quot;, &quot;.yaml&quot;);</span>
    +<span class="nc" id="L53">            try (InputStream in = Files.newInputStream(destPath)) {</span>
    +<span class="nc" id="L54">                Files.copy(in, settingTempPath, StandardCopyOption.REPLACE_EXISTING);</span>
    +            }
    +
    +<span class="nc" id="L57">            YamlHelper settingsTemplateFile = new YamlHelper(tempTemplatePath);</span>
    +<span class="nc" id="L58">            YamlHelper settingsFile = new YamlHelper(settingTempPath);</span>
    +
    +<span class="nc" id="L60">            migrateEnterpriseEditionToPremium(settingsFile, settingsTemplateFile);</span>
    +
    +<span class="nc" id="L62">            boolean changesMade =</span>
    +<span class="nc" id="L63">                    settingsTemplateFile.updateValuesFromYaml(settingsFile, settingsTemplateFile);</span>
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">            if (changesMade) {</span>
    +<span class="nc" id="L65">                settingsTemplateFile.save(destPath);</span>
    +<span class="nc" id="L66">                log.info(&quot;Settings file updated based on template changes.&quot;);</span>
    +            } else {
    +<span class="nc" id="L68">                log.info(&quot;No changes detected; settings file left as-is.&quot;);</span>
    +            }
    +
    +<span class="nc" id="L71">            Files.deleteIfExists(tempTemplatePath);</span>
    +<span class="nc" id="L72">            Files.deleteIfExists(settingTempPath);</span>
    +        }
    +
    +        // 3) Ensure custom settings file exists
    +<span class="nc" id="L76">        Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());</span>
    +<span class="nc bnc" id="L77" title="All 2 branches missed.">        if (Files.notExists(customSettingsPath)) {</span>
    +<span class="nc" id="L78">            Files.createFile(customSettingsPath);</span>
    +<span class="nc" id="L79">            log.info(&quot;Created custom_settings file: {}&quot;, customSettingsPath.toString());</span>
    +        }
    +<span class="nc" id="L81">    }</span>
    +
    +    // TODO: Remove post migration
    +    private void migrateEnterpriseEditionToPremium(YamlHelper yaml, YamlHelper template) {
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">        if (yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;enabled&quot;) != null) {</span>
    +<span class="nc" id="L86">            template.updateValue(</span>
    +<span class="nc" id="L87">                    List.of(&quot;premium&quot;, &quot;enabled&quot;),</span>
    +<span class="nc" id="L88">                    yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;enabled&quot;));</span>
    +        }
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">        if (yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;key&quot;) != null) {</span>
    +<span class="nc" id="L91">            template.updateValue(</span>
    +<span class="nc" id="L92">                    List.of(&quot;premium&quot;, &quot;key&quot;),</span>
    +<span class="nc" id="L93">                    yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;key&quot;));</span>
    +        }
    +<span class="nc bnc" id="L95" title="All 2 branches missed.">        if (yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;SSOAutoLogin&quot;) != null) {</span>
    +<span class="nc" id="L96">            template.updateValue(</span>
    +<span class="nc" id="L97">                    List.of(&quot;premium&quot;, &quot;proFeatures&quot;, &quot;SSOAutoLogin&quot;),</span>
    +<span class="nc" id="L98">                    yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;SSOAutoLogin&quot;));</span>
    +        }
    +<span class="nc bnc" id="L100" title="All 2 branches missed.">        if (yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;CustomMetadata&quot;, &quot;autoUpdateMetadata&quot;)</span>
    +                != null) {
    +<span class="nc" id="L102">            template.updateValue(</span>
    +<span class="nc" id="L103">                    List.of(&quot;premium&quot;, &quot;proFeatures&quot;, &quot;CustomMetadata&quot;, &quot;autoUpdateMetadata&quot;),</span>
    +<span class="nc" id="L104">                    yaml.getValueByExactKeyPath(</span>
    +                            &quot;enterpriseEdition&quot;, &quot;CustomMetadata&quot;, &quot;autoUpdateMetadata&quot;));
    +        }
    +<span class="nc bnc" id="L107" title="All 2 branches missed.">        if (yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;CustomMetadata&quot;, &quot;author&quot;) != null) {</span>
    +<span class="nc" id="L108">            template.updateValue(</span>
    +<span class="nc" id="L109">                    List.of(&quot;premium&quot;, &quot;proFeatures&quot;, &quot;CustomMetadata&quot;, &quot;author&quot;),</span>
    +<span class="nc" id="L110">                    yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;CustomMetadata&quot;, &quot;author&quot;));</span>
    +        }
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">        if (yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;CustomMetadata&quot;, &quot;creator&quot;) != null) {</span>
    +<span class="nc" id="L113">            template.updateValue(</span>
    +<span class="nc" id="L114">                    List.of(&quot;premium&quot;, &quot;proFeatures&quot;, &quot;CustomMetadata&quot;, &quot;creator&quot;),</span>
    +<span class="nc" id="L115">                    yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;CustomMetadata&quot;, &quot;creator&quot;));</span>
    +        }
    +<span class="nc bnc" id="L117" title="All 2 branches missed.">        if (yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;CustomMetadata&quot;, &quot;producer&quot;)</span>
    +                != null) {
    +<span class="nc" id="L119">            template.updateValue(</span>
    +<span class="nc" id="L120">                    List.of(&quot;premium&quot;, &quot;proFeatures&quot;, &quot;CustomMetadata&quot;, &quot;producer&quot;),</span>
    +<span class="nc" id="L121">                    yaml.getValueByExactKeyPath(&quot;enterpriseEdition&quot;, &quot;CustomMetadata&quot;, &quot;producer&quot;));</span>
    +        }
    +<span class="nc" id="L123">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/EndpointConfiguration.html b/testResult/html/stirling.software.SPDF.config/EndpointConfiguration.html
    new file mode 100644
    index 000000000..59f07b9f8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/EndpointConfiguration.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EndpointConfiguration</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">EndpointConfiguration</span></div><h1>EndpointConfiguration</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">802 of 802</td><td class="ctr2">0%</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="ctr1">36</td><td class="ctr2">36</td><td class="ctr1">200</td><td class="ctr2">200</td><td class="ctr1">15</td><td class="ctr2">15</td></tr></tfoot><tbody><tr><td id="a7"><a href="EndpointConfiguration.java.html#L112" class="el_method">init()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="521" alt="521"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h0">131</td><td class="ctr2" id="i0">131</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a13"><a href="EndpointConfiguration.java.html#L272" class="el_method">processEnvironmentConfigs()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="67" alt="67"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h1">16</td><td class="ctr2" id="i1">16</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a9"><a href="EndpointConfiguration.java.html#L56" class="el_method">isGroupEnabled(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="36" alt="36"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="8" alt="8"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h3">9</td><td class="ctr2" id="i3">9</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="EndpointConfiguration.java.html#L42" class="el_method">disableEndpoint(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="24" alt="24"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h7">4</td><td class="ctr2" id="i7">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a12"><a href="EndpointConfiguration.java.html#L94" class="el_method">logDisabledEndpointsSummary()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="24" alt="24"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h2">11</td><td class="ctr2" id="i2">11</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="EndpointConfiguration.java.html#L23" class="el_method">EndpointConfiguration(ApplicationProperties, boolean)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="23" alt="23"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h4">8</td><td class="ctr2" id="i4">8</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="EndpointConfiguration.java.html#L76" class="el_method">enableGroup(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="23" alt="23"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="EndpointConfiguration.java.html#L85" class="el_method">disableGroup(String)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="23" alt="23"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h6">6</td><td class="ctr2" id="i6">6</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="EndpointConfiguration.java.html#L49" class="el_method">isEndpointEnabled(String)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="17" alt="17"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h8">3</td><td class="ctr2" id="i8">3</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a0"><a href="EndpointConfiguration.java.html#L72" class="el_method">addEndpointToGroup(String, String)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="10" alt="10"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a6"><a href="EndpointConfiguration.java.html#L298" class="el_method">getEndpointsForGroup(String)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="9" alt="9"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">1</td><td class="ctr2" id="i11">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a11"><a href="EndpointConfiguration.java.html#L96" class="el_method">lambda$logDisabledEndpointsSummary$1(Map.Entry)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="9" alt="9"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h12">1</td><td class="ctr2" id="i12">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a3"><a href="EndpointConfiguration.java.html#L38" class="el_method">enableEndpoint(String)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="8" alt="8"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h10">2</td><td class="ctr2" id="i10">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a10"><a href="EndpointConfiguration.java.html#L72" class="el_method">lambda$addEndpointToGroup$0(String)</a></td><td class="bar" id="b13"/><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">1</td><td class="ctr2" id="i13">1</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a14"><a href="EndpointConfiguration.java.html#L18" class="el_method">static {...}</a></td><td class="bar" id="b14"/><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h14">1</td><td class="ctr2" id="i14">1</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/EndpointConfiguration.java.html b/testResult/html/stirling.software.SPDF.config/EndpointConfiguration.java.html
    new file mode 100644
    index 000000000..cd6536ac6
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/EndpointConfiguration.java.html
    @@ -0,0 +1,301 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EndpointConfiguration.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">EndpointConfiguration.java</span></div><h1>EndpointConfiguration.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.concurrent.ConcurrentHashMap;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.stereotype.Service;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +@Service
    +<span class="nc" id="L18">@Slf4j</span>
    +public class EndpointConfiguration {
    +
    +    private static final String REMOVE_BLANKS = &quot;remove-blanks&quot;;
    +    private final ApplicationProperties applicationProperties;
    +<span class="nc" id="L23">    private Map&lt;String, Boolean&gt; endpointStatuses = new ConcurrentHashMap&lt;&gt;();</span>
    +<span class="nc" id="L24">    private Map&lt;String, Set&lt;String&gt;&gt; endpointGroups = new ConcurrentHashMap&lt;&gt;();</span>
    +    private final boolean runningProOrHigher;
    +
    +    @Autowired
    +    public EndpointConfiguration(
    +            ApplicationProperties applicationProperties,
    +<span class="nc" id="L30">            @Qualifier(&quot;runningProOrHigher&quot;) boolean runningProOrHigher) {</span>
    +<span class="nc" id="L31">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L32">        this.runningProOrHigher = runningProOrHigher;</span>
    +<span class="nc" id="L33">        init();</span>
    +<span class="nc" id="L34">        processEnvironmentConfigs();</span>
    +<span class="nc" id="L35">    }</span>
    +
    +    public void enableEndpoint(String endpoint) {
    +<span class="nc" id="L38">        endpointStatuses.put(endpoint, true);</span>
    +<span class="nc" id="L39">    }</span>
    +
    +    public void disableEndpoint(String endpoint) {
    +<span class="nc bnc" id="L42" title="All 4 branches missed.">        if (!endpointStatuses.containsKey(endpoint) || endpointStatuses.get(endpoint) != false) {</span>
    +<span class="nc" id="L43">            log.debug(&quot;Disabling {}&quot;, endpoint);</span>
    +<span class="nc" id="L44">            endpointStatuses.put(endpoint, false);</span>
    +        }
    +<span class="nc" id="L46">    }</span>
    +
    +    public boolean isEndpointEnabled(String endpoint) {
    +<span class="nc bnc" id="L49" title="All 2 branches missed.">        if (endpoint.startsWith(&quot;/&quot;)) {</span>
    +<span class="nc" id="L50">            endpoint = endpoint.substring(1);</span>
    +        }
    +<span class="nc" id="L52">        return endpointStatuses.getOrDefault(endpoint, true);</span>
    +    }
    +
    +    public boolean isGroupEnabled(String group) {
    +<span class="nc" id="L56">        Set&lt;String&gt; endpoints = endpointGroups.get(group);</span>
    +<span class="nc bnc" id="L57" title="All 4 branches missed.">        if (endpoints == null || endpoints.isEmpty()) {</span>
    +<span class="nc" id="L58">            log.debug(&quot;Group '{}' does not exist or has no endpoints&quot;, group);</span>
    +<span class="nc" id="L59">            return false;</span>
    +        }
    +
    +<span class="nc bnc" id="L62" title="All 2 branches missed.">        for (String endpoint : endpoints) {</span>
    +<span class="nc bnc" id="L63" title="All 2 branches missed.">            if (!isEndpointEnabled(endpoint)) {</span>
    +<span class="nc" id="L64">                return false;</span>
    +            }
    +<span class="nc" id="L66">        }</span>
    +
    +<span class="nc" id="L68">        return true;</span>
    +    }
    +
    +    public void addEndpointToGroup(String group, String endpoint) {
    +<span class="nc" id="L72">        endpointGroups.computeIfAbsent(group, k -&gt; new HashSet&lt;&gt;()).add(endpoint);</span>
    +<span class="nc" id="L73">    }</span>
    +
    +    public void enableGroup(String group) {
    +<span class="nc" id="L76">        Set&lt;String&gt; endpoints = endpointGroups.get(group);</span>
    +<span class="nc bnc" id="L77" title="All 2 branches missed.">        if (endpoints != null) {</span>
    +<span class="nc bnc" id="L78" title="All 2 branches missed.">            for (String endpoint : endpoints) {</span>
    +<span class="nc" id="L79">                enableEndpoint(endpoint);</span>
    +<span class="nc" id="L80">            }</span>
    +        }
    +<span class="nc" id="L82">    }</span>
    +
    +    public void disableGroup(String group) {
    +<span class="nc" id="L85">        Set&lt;String&gt; endpoints = endpointGroups.get(group);</span>
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">        if (endpoints != null) {</span>
    +<span class="nc bnc" id="L87" title="All 2 branches missed.">            for (String endpoint : endpoints) {</span>
    +<span class="nc" id="L88">                disableEndpoint(endpoint);</span>
    +<span class="nc" id="L89">            }</span>
    +        }
    +<span class="nc" id="L91">    }</span>
    +
    +    public void logDisabledEndpointsSummary() {
    +<span class="nc" id="L94">        List&lt;String&gt; disabledList =</span>
    +<span class="nc" id="L95">                endpointStatuses.entrySet().stream()</span>
    +<span class="nc bnc" id="L96" title="All 2 branches missed.">                        .filter(entry -&gt; !entry.getValue()) // only get disabled endpoints (value</span>
    +                        // is false)
    +<span class="nc" id="L98">                        .map(Map.Entry::getKey)</span>
    +<span class="nc" id="L99">                        .sorted()</span>
    +<span class="nc" id="L100">                        .toList();</span>
    +
    +<span class="nc bnc" id="L102" title="All 2 branches missed.">        if (!disabledList.isEmpty()) {</span>
    +<span class="nc" id="L103">            log.info(</span>
    +                    &quot;Total disabled endpoints: {}. Disabled endpoints: {}&quot;,
    +<span class="nc" id="L105">                    disabledList.size(),</span>
    +<span class="nc" id="L106">                    String.join(&quot;, &quot;, disabledList));</span>
    +        }
    +<span class="nc" id="L108">    }</span>
    +
    +    public void init() {
    +        // Adding endpoints to &quot;PageOps&quot; group
    +<span class="nc" id="L112">        addEndpointToGroup(&quot;PageOps&quot;, &quot;remove-pages&quot;);</span>
    +<span class="nc" id="L113">        addEndpointToGroup(&quot;PageOps&quot;, &quot;merge-pdfs&quot;);</span>
    +<span class="nc" id="L114">        addEndpointToGroup(&quot;PageOps&quot;, &quot;split-pdfs&quot;);</span>
    +<span class="nc" id="L115">        addEndpointToGroup(&quot;PageOps&quot;, &quot;pdf-organizer&quot;);</span>
    +<span class="nc" id="L116">        addEndpointToGroup(&quot;PageOps&quot;, &quot;rotate-pdf&quot;);</span>
    +<span class="nc" id="L117">        addEndpointToGroup(&quot;PageOps&quot;, &quot;multi-page-layout&quot;);</span>
    +<span class="nc" id="L118">        addEndpointToGroup(&quot;PageOps&quot;, &quot;scale-pages&quot;);</span>
    +<span class="nc" id="L119">        addEndpointToGroup(&quot;PageOps&quot;, &quot;adjust-contrast&quot;);</span>
    +<span class="nc" id="L120">        addEndpointToGroup(&quot;PageOps&quot;, &quot;crop&quot;);</span>
    +<span class="nc" id="L121">        addEndpointToGroup(&quot;PageOps&quot;, &quot;auto-split-pdf&quot;);</span>
    +<span class="nc" id="L122">        addEndpointToGroup(&quot;PageOps&quot;, &quot;extract-page&quot;);</span>
    +<span class="nc" id="L123">        addEndpointToGroup(&quot;PageOps&quot;, &quot;pdf-to-single-page&quot;);</span>
    +<span class="nc" id="L124">        addEndpointToGroup(&quot;PageOps&quot;, &quot;split-by-size-or-count&quot;);</span>
    +<span class="nc" id="L125">        addEndpointToGroup(&quot;PageOps&quot;, &quot;overlay-pdf&quot;);</span>
    +<span class="nc" id="L126">        addEndpointToGroup(&quot;PageOps&quot;, &quot;split-pdf-by-sections&quot;);</span>
    +
    +        // Adding endpoints to &quot;Convert&quot; group
    +<span class="nc" id="L129">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-img&quot;);</span>
    +<span class="nc" id="L130">        addEndpointToGroup(&quot;Convert&quot;, &quot;img-to-pdf&quot;);</span>
    +<span class="nc" id="L131">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-pdfa&quot;);</span>
    +<span class="nc" id="L132">        addEndpointToGroup(&quot;Convert&quot;, &quot;file-to-pdf&quot;);</span>
    +<span class="nc" id="L133">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-word&quot;);</span>
    +<span class="nc" id="L134">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-presentation&quot;);</span>
    +<span class="nc" id="L135">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-text&quot;);</span>
    +<span class="nc" id="L136">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-html&quot;);</span>
    +<span class="nc" id="L137">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-xml&quot;);</span>
    +<span class="nc" id="L138">        addEndpointToGroup(&quot;Convert&quot;, &quot;html-to-pdf&quot;);</span>
    +<span class="nc" id="L139">        addEndpointToGroup(&quot;Convert&quot;, &quot;url-to-pdf&quot;);</span>
    +<span class="nc" id="L140">        addEndpointToGroup(&quot;Convert&quot;, &quot;markdown-to-pdf&quot;);</span>
    +<span class="nc" id="L141">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-csv&quot;);</span>
    +<span class="nc" id="L142">        addEndpointToGroup(&quot;Convert&quot;, &quot;pdf-to-markdown&quot;);</span>
    +
    +        // Adding endpoints to &quot;Security&quot; group
    +<span class="nc" id="L145">        addEndpointToGroup(&quot;Security&quot;, &quot;add-password&quot;);</span>
    +<span class="nc" id="L146">        addEndpointToGroup(&quot;Security&quot;, &quot;remove-password&quot;);</span>
    +<span class="nc" id="L147">        addEndpointToGroup(&quot;Security&quot;, &quot;change-permissions&quot;);</span>
    +<span class="nc" id="L148">        addEndpointToGroup(&quot;Security&quot;, &quot;add-watermark&quot;);</span>
    +<span class="nc" id="L149">        addEndpointToGroup(&quot;Security&quot;, &quot;cert-sign&quot;);</span>
    +<span class="nc" id="L150">        addEndpointToGroup(&quot;Security&quot;, &quot;remove-cert-sign&quot;);</span>
    +<span class="nc" id="L151">        addEndpointToGroup(&quot;Security&quot;, &quot;sanitize-pdf&quot;);</span>
    +<span class="nc" id="L152">        addEndpointToGroup(&quot;Security&quot;, &quot;auto-redact&quot;);</span>
    +<span class="nc" id="L153">        addEndpointToGroup(&quot;Security&quot;, &quot;redact&quot;);</span>
    +
    +        // Adding endpoints to &quot;Other&quot; group
    +<span class="nc" id="L156">        addEndpointToGroup(&quot;Other&quot;, &quot;ocr-pdf&quot;);</span>
    +<span class="nc" id="L157">        addEndpointToGroup(&quot;Other&quot;, &quot;add-image&quot;);</span>
    +<span class="nc" id="L158">        addEndpointToGroup(&quot;Other&quot;, &quot;compress-pdf&quot;);</span>
    +<span class="nc" id="L159">        addEndpointToGroup(&quot;Other&quot;, &quot;extract-images&quot;);</span>
    +<span class="nc" id="L160">        addEndpointToGroup(&quot;Other&quot;, &quot;change-metadata&quot;);</span>
    +<span class="nc" id="L161">        addEndpointToGroup(&quot;Other&quot;, &quot;extract-image-scans&quot;);</span>
    +<span class="nc" id="L162">        addEndpointToGroup(&quot;Other&quot;, &quot;sign&quot;);</span>
    +<span class="nc" id="L163">        addEndpointToGroup(&quot;Other&quot;, &quot;flatten&quot;);</span>
    +<span class="nc" id="L164">        addEndpointToGroup(&quot;Other&quot;, &quot;repair&quot;);</span>
    +<span class="nc" id="L165">        addEndpointToGroup(&quot;Other&quot;, REMOVE_BLANKS);</span>
    +<span class="nc" id="L166">        addEndpointToGroup(&quot;Other&quot;, &quot;remove-annotations&quot;);</span>
    +<span class="nc" id="L167">        addEndpointToGroup(&quot;Other&quot;, &quot;compare&quot;);</span>
    +<span class="nc" id="L168">        addEndpointToGroup(&quot;Other&quot;, &quot;add-page-numbers&quot;);</span>
    +<span class="nc" id="L169">        addEndpointToGroup(&quot;Other&quot;, &quot;auto-rename&quot;);</span>
    +<span class="nc" id="L170">        addEndpointToGroup(&quot;Other&quot;, &quot;get-info-on-pdf&quot;);</span>
    +<span class="nc" id="L171">        addEndpointToGroup(&quot;Other&quot;, &quot;show-javascript&quot;);</span>
    +<span class="nc" id="L172">        addEndpointToGroup(&quot;Other&quot;, &quot;remove-image-pdf&quot;);</span>
    +
    +        // CLI
    +<span class="nc" id="L175">        addEndpointToGroup(&quot;CLI&quot;, &quot;compress-pdf&quot;);</span>
    +<span class="nc" id="L176">        addEndpointToGroup(&quot;CLI&quot;, &quot;extract-image-scans&quot;);</span>
    +<span class="nc" id="L177">        addEndpointToGroup(&quot;CLI&quot;, &quot;repair&quot;);</span>
    +<span class="nc" id="L178">        addEndpointToGroup(&quot;CLI&quot;, &quot;pdf-to-pdfa&quot;);</span>
    +<span class="nc" id="L179">        addEndpointToGroup(&quot;CLI&quot;, &quot;file-to-pdf&quot;);</span>
    +<span class="nc" id="L180">        addEndpointToGroup(&quot;CLI&quot;, &quot;pdf-to-word&quot;);</span>
    +<span class="nc" id="L181">        addEndpointToGroup(&quot;CLI&quot;, &quot;pdf-to-presentation&quot;);</span>
    +<span class="nc" id="L182">        addEndpointToGroup(&quot;CLI&quot;, &quot;pdf-to-html&quot;);</span>
    +<span class="nc" id="L183">        addEndpointToGroup(&quot;CLI&quot;, &quot;pdf-to-xml&quot;);</span>
    +<span class="nc" id="L184">        addEndpointToGroup(&quot;CLI&quot;, &quot;ocr-pdf&quot;);</span>
    +<span class="nc" id="L185">        addEndpointToGroup(&quot;CLI&quot;, &quot;html-to-pdf&quot;);</span>
    +<span class="nc" id="L186">        addEndpointToGroup(&quot;CLI&quot;, &quot;url-to-pdf&quot;);</span>
    +<span class="nc" id="L187">        addEndpointToGroup(&quot;CLI&quot;, &quot;pdf-to-rtf&quot;);</span>
    +
    +        // python
    +<span class="nc" id="L190">        addEndpointToGroup(&quot;Python&quot;, &quot;extract-image-scans&quot;);</span>
    +<span class="nc" id="L191">        addEndpointToGroup(&quot;Python&quot;, &quot;html-to-pdf&quot;);</span>
    +<span class="nc" id="L192">        addEndpointToGroup(&quot;Python&quot;, &quot;url-to-pdf&quot;);</span>
    +<span class="nc" id="L193">        addEndpointToGroup(&quot;Python&quot;, &quot;file-to-pdf&quot;);</span>
    +
    +        // openCV
    +<span class="nc" id="L196">        addEndpointToGroup(&quot;OpenCV&quot;, &quot;extract-image-scans&quot;);</span>
    +
    +        // LibreOffice
    +<span class="nc" id="L199">        addEndpointToGroup(&quot;LibreOffice&quot;, &quot;file-to-pdf&quot;);</span>
    +<span class="nc" id="L200">        addEndpointToGroup(&quot;LibreOffice&quot;, &quot;pdf-to-word&quot;);</span>
    +<span class="nc" id="L201">        addEndpointToGroup(&quot;LibreOffice&quot;, &quot;pdf-to-presentation&quot;);</span>
    +<span class="nc" id="L202">        addEndpointToGroup(&quot;LibreOffice&quot;, &quot;pdf-to-rtf&quot;);</span>
    +<span class="nc" id="L203">        addEndpointToGroup(&quot;LibreOffice&quot;, &quot;pdf-to-html&quot;);</span>
    +<span class="nc" id="L204">        addEndpointToGroup(&quot;LibreOffice&quot;, &quot;pdf-to-xml&quot;);</span>
    +<span class="nc" id="L205">        addEndpointToGroup(&quot;LibreOffice&quot;, &quot;pdf-to-pdfa&quot;);</span>
    +
    +        // Unoconvert
    +<span class="nc" id="L208">        addEndpointToGroup(&quot;Unoconvert&quot;, &quot;file-to-pdf&quot;);</span>
    +
    +<span class="nc" id="L210">        addEndpointToGroup(&quot;tesseract&quot;, &quot;ocr-pdf&quot;);</span>
    +
    +        // Java
    +<span class="nc" id="L213">        addEndpointToGroup(&quot;Java&quot;, &quot;merge-pdfs&quot;);</span>
    +<span class="nc" id="L214">        addEndpointToGroup(&quot;Java&quot;, &quot;remove-pages&quot;);</span>
    +<span class="nc" id="L215">        addEndpointToGroup(&quot;Java&quot;, &quot;split-pdfs&quot;);</span>
    +<span class="nc" id="L216">        addEndpointToGroup(&quot;Java&quot;, &quot;pdf-organizer&quot;);</span>
    +<span class="nc" id="L217">        addEndpointToGroup(&quot;Java&quot;, &quot;rotate-pdf&quot;);</span>
    +<span class="nc" id="L218">        addEndpointToGroup(&quot;Java&quot;, &quot;pdf-to-img&quot;);</span>
    +<span class="nc" id="L219">        addEndpointToGroup(&quot;Java&quot;, &quot;img-to-pdf&quot;);</span>
    +<span class="nc" id="L220">        addEndpointToGroup(&quot;Java&quot;, &quot;add-password&quot;);</span>
    +<span class="nc" id="L221">        addEndpointToGroup(&quot;Java&quot;, &quot;remove-password&quot;);</span>
    +<span class="nc" id="L222">        addEndpointToGroup(&quot;Java&quot;, &quot;change-permissions&quot;);</span>
    +<span class="nc" id="L223">        addEndpointToGroup(&quot;Java&quot;, &quot;add-watermark&quot;);</span>
    +<span class="nc" id="L224">        addEndpointToGroup(&quot;Java&quot;, &quot;add-image&quot;);</span>
    +<span class="nc" id="L225">        addEndpointToGroup(&quot;Java&quot;, &quot;extract-images&quot;);</span>
    +<span class="nc" id="L226">        addEndpointToGroup(&quot;Java&quot;, &quot;change-metadata&quot;);</span>
    +<span class="nc" id="L227">        addEndpointToGroup(&quot;Java&quot;, &quot;cert-sign&quot;);</span>
    +<span class="nc" id="L228">        addEndpointToGroup(&quot;Java&quot;, &quot;remove-cert-sign&quot;);</span>
    +<span class="nc" id="L229">        addEndpointToGroup(&quot;Java&quot;, &quot;multi-page-layout&quot;);</span>
    +<span class="nc" id="L230">        addEndpointToGroup(&quot;Java&quot;, &quot;scale-pages&quot;);</span>
    +<span class="nc" id="L231">        addEndpointToGroup(&quot;Java&quot;, &quot;add-page-numbers&quot;);</span>
    +<span class="nc" id="L232">        addEndpointToGroup(&quot;Java&quot;, &quot;auto-rename&quot;);</span>
    +<span class="nc" id="L233">        addEndpointToGroup(&quot;Java&quot;, &quot;auto-split-pdf&quot;);</span>
    +<span class="nc" id="L234">        addEndpointToGroup(&quot;Java&quot;, &quot;sanitize-pdf&quot;);</span>
    +<span class="nc" id="L235">        addEndpointToGroup(&quot;Java&quot;, &quot;crop&quot;);</span>
    +<span class="nc" id="L236">        addEndpointToGroup(&quot;Java&quot;, &quot;get-info-on-pdf&quot;);</span>
    +<span class="nc" id="L237">        addEndpointToGroup(&quot;Java&quot;, &quot;extract-page&quot;);</span>
    +<span class="nc" id="L238">        addEndpointToGroup(&quot;Java&quot;, &quot;pdf-to-single-page&quot;);</span>
    +<span class="nc" id="L239">        addEndpointToGroup(&quot;Java&quot;, &quot;markdown-to-pdf&quot;);</span>
    +<span class="nc" id="L240">        addEndpointToGroup(&quot;Java&quot;, &quot;show-javascript&quot;);</span>
    +<span class="nc" id="L241">        addEndpointToGroup(&quot;Java&quot;, &quot;auto-redact&quot;);</span>
    +<span class="nc" id="L242">        addEndpointToGroup(&quot;Java&quot;, &quot;redact&quot;);</span>
    +<span class="nc" id="L243">        addEndpointToGroup(&quot;Java&quot;, &quot;pdf-to-csv&quot;);</span>
    +<span class="nc" id="L244">        addEndpointToGroup(&quot;Java&quot;, &quot;split-by-size-or-count&quot;);</span>
    +<span class="nc" id="L245">        addEndpointToGroup(&quot;Java&quot;, &quot;overlay-pdf&quot;);</span>
    +<span class="nc" id="L246">        addEndpointToGroup(&quot;Java&quot;, &quot;split-pdf-by-sections&quot;);</span>
    +<span class="nc" id="L247">        addEndpointToGroup(&quot;Java&quot;, REMOVE_BLANKS);</span>
    +<span class="nc" id="L248">        addEndpointToGroup(&quot;Java&quot;, &quot;pdf-to-text&quot;);</span>
    +<span class="nc" id="L249">        addEndpointToGroup(&quot;Java&quot;, &quot;remove-image-pdf&quot;);</span>
    +<span class="nc" id="L250">        addEndpointToGroup(&quot;Java&quot;, &quot;pdf-to-markdown&quot;);</span>
    +
    +        // Javascript
    +<span class="nc" id="L253">        addEndpointToGroup(&quot;Javascript&quot;, &quot;pdf-organizer&quot;);</span>
    +<span class="nc" id="L254">        addEndpointToGroup(&quot;Javascript&quot;, &quot;sign&quot;);</span>
    +<span class="nc" id="L255">        addEndpointToGroup(&quot;Javascript&quot;, &quot;compare&quot;);</span>
    +<span class="nc" id="L256">        addEndpointToGroup(&quot;Javascript&quot;, &quot;adjust-contrast&quot;);</span>
    +
    +        // qpdf dependent endpoints
    +<span class="nc" id="L259">        addEndpointToGroup(&quot;qpdf&quot;, &quot;repair&quot;);</span>
    +
    +        // Weasyprint dependent endpoints
    +<span class="nc" id="L262">        addEndpointToGroup(&quot;Weasyprint&quot;, &quot;html-to-pdf&quot;);</span>
    +<span class="nc" id="L263">        addEndpointToGroup(&quot;Weasyprint&quot;, &quot;url-to-pdf&quot;);</span>
    +<span class="nc" id="L264">        addEndpointToGroup(&quot;Weasyprint&quot;, &quot;markdown-to-pdf&quot;);</span>
    +
    +        // Pdftohtml dependent endpoints
    +<span class="nc" id="L267">        addEndpointToGroup(&quot;Pdftohtml&quot;, &quot;pdf-to-html&quot;);</span>
    +<span class="nc" id="L268">        addEndpointToGroup(&quot;Pdftohtml&quot;, &quot;pdf-to-markdown&quot;);</span>
    +<span class="nc" id="L269">    }</span>
    +
    +    private void processEnvironmentConfigs() {
    +<span class="nc bnc" id="L272" title="All 4 branches missed.">        if (applicationProperties != null &amp;&amp; applicationProperties.getEndpoints() != null) {</span>
    +<span class="nc" id="L273">            List&lt;String&gt; endpointsToRemove = applicationProperties.getEndpoints().getToRemove();</span>
    +<span class="nc" id="L274">            List&lt;String&gt; groupsToRemove = applicationProperties.getEndpoints().getGroupsToRemove();</span>
    +
    +<span class="nc bnc" id="L276" title="All 2 branches missed.">            if (endpointsToRemove != null) {</span>
    +<span class="nc bnc" id="L277" title="All 2 branches missed.">                for (String endpoint : endpointsToRemove) {</span>
    +<span class="nc" id="L278">                    disableEndpoint(endpoint.trim());</span>
    +<span class="nc" id="L279">                }</span>
    +            }
    +
    +<span class="nc bnc" id="L282" title="All 2 branches missed.">            if (groupsToRemove != null) {</span>
    +<span class="nc bnc" id="L283" title="All 2 branches missed.">                for (String group : groupsToRemove) {</span>
    +<span class="nc" id="L284">                    disableGroup(group.trim());</span>
    +<span class="nc" id="L285">                }</span>
    +            }
    +        }
    +<span class="nc bnc" id="L288" title="All 2 branches missed.">        if (!runningProOrHigher) {</span>
    +<span class="nc" id="L289">            disableGroup(&quot;enterprise&quot;);</span>
    +        }
    +
    +<span class="nc bnc" id="L292" title="All 2 branches missed.">        if (!applicationProperties.getSystem().getEnableUrlToPDF()) {</span>
    +<span class="nc" id="L293">            disableEndpoint(&quot;url-to-pdf&quot;);</span>
    +        }
    +<span class="nc" id="L295">    }</span>
    +
    +    public Set&lt;String&gt; getEndpointsForGroup(String group) {
    +<span class="nc" id="L298">        return endpointGroups.getOrDefault(group, new HashSet&lt;&gt;());</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/EndpointInspector.html b/testResult/html/stirling.software.SPDF.config/EndpointInspector.html
    new file mode 100644
    index 000000000..6cc92dec2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/EndpointInspector.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EndpointInspector</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">EndpointInspector</span></div><h1>EndpointInspector</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">411 of 411</td><td class="ctr2">0%</td><td class="bar">62 of 62</td><td class="ctr2">0%</td><td class="ctr1">42</td><td class="ctr2">42</td><td class="ctr1">111</td><td class="ctr2">111</td><td class="ctr1">11</td><td class="ctr2">11</td></tr></tfoot><tbody><tr><td id="a0"><a href="EndpointInspector.java.html#L44" class="el_method">discoverEndpoints()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="112" alt="112"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h0">30</td><td class="ctr2" id="i0">30</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a7"><a href="EndpointInspector.java.html#L173" class="el_method">matchesPathSegments(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="65" height="10" title="61" alt="61"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">8</td><td class="ctr2" id="g1">8</td><td class="ctr1" id="h1">15</td><td class="ctr2" id="i1">15</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a8"><a href="EndpointInspector.java.html#L148" class="el_method">matchesWildcardOrPathVariable(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="58" height="10" title="55" alt="55"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="102" height="10" title="12" alt="12"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">7</td><td class="ctr2" id="g2">7</td><td class="ctr1" id="h2">14</td><td class="ctr2" id="i2">14</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="EndpointInspector.java.html#L106" class="el_method">extractPatternsFromString(RequestMappingInfo)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="57" height="10" title="54" alt="54"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f4">4</td><td class="ctr2" id="g4">4</td><td class="ctr1" id="h3">12</td><td class="ctr2" id="i3">12</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="EndpointInspector.java.html#L88" class="el_method">extractPatternsUsingDirectPaths(RequestMappingInfo)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="31" alt="31"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h5">9</td><td class="ctr2" id="i5">9</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="EndpointInspector.java.html#L127" class="el_method">isValidGetEndpoint(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="29" alt="29"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="8" alt="8"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f3">5</td><td class="ctr2" id="g3">5</td><td class="ctr1" id="h4">10</td><td class="ctr2" id="i4">10</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="EndpointInspector.java.html#L207" class="el_method">logAllEndpoints()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="28" alt="28"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h6">7</td><td class="ctr2" id="i6">7</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a1"><a href="EndpointInspector.java.html#L26" class="el_method">EndpointInspector(ApplicationContext)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="14" alt="14"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h7">5</td><td class="ctr2" id="i7">5</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a4"><a href="EndpointInspector.java.html#L199" class="el_method">getValidGetEndpoints()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="14" alt="14"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h8">4</td><td class="ctr2" id="i8">4</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a9"><a href="EndpointInspector.java.html#L36" class="el_method">onApplicationEvent(ContextRefreshedEvent)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="9" alt="9"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h9">4</td><td class="ctr2" id="i9">4</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a10"><a href="EndpointInspector.java.html#L23" class="el_method">static {...}</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/EndpointInspector.java.html b/testResult/html/stirling.software.SPDF.config/EndpointInspector.java.html
    new file mode 100644
    index 000000000..42a24386b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/EndpointInspector.java.html
    @@ -0,0 +1,216 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EndpointInspector.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">EndpointInspector.java</span></div><h1>EndpointInspector.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.lang.reflect.Method;
    +import java.util.HashSet;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.TreeSet;
    +
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.context.ApplicationContext;
    +import org.springframework.context.ApplicationListener;
    +import org.springframework.context.event.ContextRefreshedEvent;
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.bind.annotation.RequestMethod;
    +import org.springframework.web.method.HandlerMethod;
    +import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
    +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
    +
    +@Component
    +public class EndpointInspector implements ApplicationListener&lt;ContextRefreshedEvent&gt; {
    +<span class="nc" id="L23">    private static final Logger logger = LoggerFactory.getLogger(EndpointInspector.class);</span>
    +
    +    private final ApplicationContext applicationContext;
    +<span class="nc" id="L26">    private final Set&lt;String&gt; validGetEndpoints = new HashSet&lt;&gt;();</span>
    +<span class="nc" id="L27">    private boolean endpointsDiscovered = false;</span>
    +
    +    @Autowired
    +<span class="nc" id="L30">    public EndpointInspector(ApplicationContext applicationContext) {</span>
    +<span class="nc" id="L31">        this.applicationContext = applicationContext;</span>
    +<span class="nc" id="L32">    }</span>
    +
    +    @Override
    +    public void onApplicationEvent(ContextRefreshedEvent event) {
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">        if (!endpointsDiscovered) {</span>
    +<span class="nc" id="L37">            discoverEndpoints();</span>
    +<span class="nc" id="L38">            endpointsDiscovered = true;</span>
    +        }
    +<span class="nc" id="L40">    }</span>
    +
    +    private void discoverEndpoints() {
    +        try {
    +<span class="nc" id="L44">            Map&lt;String, RequestMappingHandlerMapping&gt; mappings =</span>
    +<span class="nc" id="L45">                    applicationContext.getBeansOfType(RequestMappingHandlerMapping.class);</span>
    +
    +<span class="nc bnc" id="L47" title="All 2 branches missed.">            for (Map.Entry&lt;String, RequestMappingHandlerMapping&gt; entry : mappings.entrySet()) {</span>
    +<span class="nc" id="L48">                RequestMappingHandlerMapping mapping = entry.getValue();</span>
    +<span class="nc" id="L49">                Map&lt;RequestMappingInfo, HandlerMethod&gt; handlerMethods = mapping.getHandlerMethods();</span>
    +
    +                for (Map.Entry&lt;RequestMappingInfo, HandlerMethod&gt; handlerEntry :
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">                        handlerMethods.entrySet()) {</span>
    +<span class="nc" id="L53">                    RequestMappingInfo mappingInfo = handlerEntry.getKey();</span>
    +<span class="nc" id="L54">                    HandlerMethod handlerMethod = handlerEntry.getValue();</span>
    +
    +<span class="nc" id="L56">                    boolean isGetHandler = false;</span>
    +                    try {
    +<span class="nc" id="L58">                        Set&lt;RequestMethod&gt; methods = mappingInfo.getMethodsCondition().getMethods();</span>
    +<span class="nc bnc" id="L59" title="All 4 branches missed.">                        isGetHandler = methods.isEmpty() || methods.contains(RequestMethod.GET);</span>
    +<span class="nc" id="L60">                    } catch (Exception e) {</span>
    +<span class="nc" id="L61">                        isGetHandler = true;</span>
    +<span class="nc" id="L62">                    }</span>
    +
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">                    if (isGetHandler) {</span>
    +<span class="nc" id="L65">                        Set&lt;String&gt; patterns = extractPatternsUsingDirectPaths(mappingInfo);</span>
    +
    +<span class="nc bnc" id="L67" title="All 2 branches missed.">                        if (patterns.isEmpty()) {</span>
    +<span class="nc" id="L68">                            patterns = extractPatternsFromString(mappingInfo);</span>
    +                        }
    +
    +<span class="nc" id="L71">                        validGetEndpoints.addAll(patterns);</span>
    +                    }
    +<span class="nc" id="L73">                }</span>
    +<span class="nc" id="L74">            }</span>
    +
    +<span class="nc bnc" id="L76" title="All 2 branches missed.">            if (validGetEndpoints.isEmpty()) {</span>
    +<span class="nc" id="L77">                logger.warn(&quot;No endpoints discovered. Adding common endpoints as fallback.&quot;);</span>
    +<span class="nc" id="L78">                validGetEndpoints.add(&quot;/&quot;);</span>
    +<span class="nc" id="L79">                validGetEndpoints.add(&quot;/api/**&quot;);</span>
    +<span class="nc" id="L80">                validGetEndpoints.add(&quot;/**&quot;);</span>
    +            }
    +<span class="nc" id="L82">        } catch (Exception e) {</span>
    +<span class="nc" id="L83">            logger.error(&quot;Error discovering endpoints&quot;, e);</span>
    +<span class="nc" id="L84">        }</span>
    +<span class="nc" id="L85">    }</span>
    +
    +    private Set&lt;String&gt; extractPatternsUsingDirectPaths(RequestMappingInfo mappingInfo) {
    +<span class="nc" id="L88">        Set&lt;String&gt; patterns = new HashSet&lt;&gt;();</span>
    +
    +        try {
    +<span class="nc" id="L91">            Method getDirectPathsMethod = mappingInfo.getClass().getMethod(&quot;getDirectPaths&quot;);</span>
    +<span class="nc" id="L92">            Object result = getDirectPathsMethod.invoke(mappingInfo);</span>
    +<span class="nc bnc" id="L93" title="All 2 branches missed.">            if (result instanceof Set) {</span>
    +                @SuppressWarnings(&quot;unchecked&quot;)
    +<span class="nc" id="L95">                Set&lt;String&gt; resultSet = (Set&lt;String&gt;) result;</span>
    +<span class="nc" id="L96">                patterns.addAll(resultSet);</span>
    +            }
    +<span class="nc" id="L98">        } catch (Exception e) {</span>
    +            // Return empty set if method not found or fails
    +<span class="nc" id="L100">        }</span>
    +
    +<span class="nc" id="L102">        return patterns;</span>
    +    }
    +
    +    private Set&lt;String&gt; extractPatternsFromString(RequestMappingInfo mappingInfo) {
    +<span class="nc" id="L106">        Set&lt;String&gt; patterns = new HashSet&lt;&gt;();</span>
    +        try {
    +<span class="nc" id="L108">            String infoString = mappingInfo.toString();</span>
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">            if (infoString.contains(&quot;{&quot;)) {</span>
    +<span class="nc" id="L110">                String patternsSection =</span>
    +<span class="nc" id="L111">                        infoString.substring(infoString.indexOf(&quot;{&quot;) + 1, infoString.indexOf(&quot;}&quot;));</span>
    +
    +<span class="nc bnc" id="L113" title="All 2 branches missed.">                for (String pattern : patternsSection.split(&quot;,&quot;)) {</span>
    +<span class="nc" id="L114">                    pattern = pattern.trim();</span>
    +<span class="nc bnc" id="L115" title="All 2 branches missed.">                    if (!pattern.isEmpty()) {</span>
    +<span class="nc" id="L116">                        patterns.add(pattern);</span>
    +                    }
    +                }
    +            }
    +<span class="nc" id="L120">        } catch (Exception e) {</span>
    +            // Return empty set if parsing fails
    +<span class="nc" id="L122">        }</span>
    +<span class="nc" id="L123">        return patterns;</span>
    +    }
    +
    +    public boolean isValidGetEndpoint(String uri) {
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">        if (!endpointsDiscovered) {</span>
    +<span class="nc" id="L128">            discoverEndpoints();</span>
    +<span class="nc" id="L129">            endpointsDiscovered = true;</span>
    +        }
    +
    +<span class="nc bnc" id="L132" title="All 2 branches missed.">        if (validGetEndpoints.contains(uri)) {</span>
    +<span class="nc" id="L133">            return true;</span>
    +        }
    +
    +<span class="nc bnc" id="L136" title="All 2 branches missed.">        if (matchesWildcardOrPathVariable(uri)) {</span>
    +<span class="nc" id="L137">            return true;</span>
    +        }
    +
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">        if (matchesPathSegments(uri)) {</span>
    +<span class="nc" id="L141">            return true;</span>
    +        }
    +
    +<span class="nc" id="L144">        return false;</span>
    +    }
    +
    +    private boolean matchesWildcardOrPathVariable(String uri) {
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">        for (String pattern : validGetEndpoints) {</span>
    +<span class="nc bnc" id="L149" title="All 4 branches missed.">            if (pattern.contains(&quot;*&quot;) || pattern.contains(&quot;{&quot;)) {</span>
    +<span class="nc" id="L150">                int wildcardIndex = pattern.indexOf('*');</span>
    +<span class="nc" id="L151">                int variableIndex = pattern.indexOf('{');</span>
    +
    +                int cutoffIndex;
    +<span class="nc bnc" id="L154" title="All 2 branches missed.">                if (wildcardIndex &lt; 0) {</span>
    +<span class="nc" id="L155">                    cutoffIndex = variableIndex;</span>
    +<span class="nc bnc" id="L156" title="All 2 branches missed.">                } else if (variableIndex &lt; 0) {</span>
    +<span class="nc" id="L157">                    cutoffIndex = wildcardIndex;</span>
    +                } else {
    +<span class="nc" id="L159">                    cutoffIndex = Math.min(wildcardIndex, variableIndex);</span>
    +                }
    +
    +<span class="nc" id="L162">                String staticPrefix = pattern.substring(0, cutoffIndex);</span>
    +
    +<span class="nc bnc" id="L164" title="All 2 branches missed.">                if (uri.startsWith(staticPrefix)) {</span>
    +<span class="nc" id="L165">                    return true;</span>
    +                }
    +            }
    +<span class="nc" id="L168">        }</span>
    +<span class="nc" id="L169">        return false;</span>
    +    }
    +
    +    private boolean matchesPathSegments(String uri) {
    +<span class="nc bnc" id="L173" title="All 2 branches missed.">        for (String pattern : validGetEndpoints) {</span>
    +<span class="nc bnc" id="L174" title="All 4 branches missed.">            if (!pattern.contains(&quot;*&quot;) &amp;&amp; !pattern.contains(&quot;{&quot;)) {</span>
    +<span class="nc" id="L175">                String[] patternSegments = pattern.split(&quot;/&quot;);</span>
    +<span class="nc" id="L176">                String[] uriSegments = uri.split(&quot;/&quot;);</span>
    +
    +<span class="nc bnc" id="L178" title="All 2 branches missed.">                if (uriSegments.length &lt; patternSegments.length) {</span>
    +<span class="nc" id="L179">                    continue;</span>
    +                }
    +
    +<span class="nc" id="L182">                boolean match = true;</span>
    +<span class="nc bnc" id="L183" title="All 2 branches missed.">                for (int i = 0; i &lt; patternSegments.length; i++) {</span>
    +<span class="nc bnc" id="L184" title="All 2 branches missed.">                    if (!patternSegments[i].equals(uriSegments[i])) {</span>
    +<span class="nc" id="L185">                        match = false;</span>
    +<span class="nc" id="L186">                        break;</span>
    +                    }
    +                }
    +
    +<span class="nc bnc" id="L190" title="All 2 branches missed.">                if (match) {</span>
    +<span class="nc" id="L191">                    return true;</span>
    +                }
    +            }
    +<span class="nc" id="L194">        }</span>
    +<span class="nc" id="L195">        return false;</span>
    +    }
    +
    +    public Set&lt;String&gt; getValidGetEndpoints() {
    +<span class="nc bnc" id="L199" title="All 2 branches missed.">        if (!endpointsDiscovered) {</span>
    +<span class="nc" id="L200">            discoverEndpoints();</span>
    +<span class="nc" id="L201">            endpointsDiscovered = true;</span>
    +        }
    +<span class="nc" id="L203">        return new HashSet&lt;&gt;(validGetEndpoints);</span>
    +    }
    +
    +    private void logAllEndpoints() {
    +<span class="nc" id="L207">        Set&lt;String&gt; sortedEndpoints = new TreeSet&lt;&gt;(validGetEndpoints);</span>
    +
    +<span class="nc" id="L209">        logger.info(&quot;=== BEGIN: All discovered GET endpoints ===&quot;);</span>
    +<span class="nc bnc" id="L210" title="All 2 branches missed.">        for (String endpoint : sortedEndpoints) {</span>
    +<span class="nc" id="L211">            logger.info(&quot;Endpoint: {}&quot;, endpoint);</span>
    +<span class="nc" id="L212">        }</span>
    +<span class="nc" id="L213">        logger.info(&quot;=== END: All discovered GET endpoints ===&quot;);</span>
    +<span class="nc" id="L214">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/EndpointInterceptor.html b/testResult/html/stirling.software.SPDF.config/EndpointInterceptor.html
    new file mode 100644
    index 000000000..48319f30f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/EndpointInterceptor.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EndpointInterceptor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">EndpointInterceptor</span></div><h1>EndpointInterceptor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">26 of 26</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="EndpointInterceptor.java.html#L25" class="el_method">preHandle(HttpServletRequest, HttpServletResponse, Object)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="EndpointInterceptor.java.html#L17" class="el_method">EndpointInterceptor(EndpointConfiguration)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="EndpointInterceptor.java.html#L12" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/EndpointInterceptor.java.html b/testResult/html/stirling.software.SPDF.config/EndpointInterceptor.java.html
    new file mode 100644
    index 000000000..01783fbce
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/EndpointInterceptor.java.html
    @@ -0,0 +1,33 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EndpointInterceptor.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">EndpointInterceptor.java</span></div><h1>EndpointInterceptor.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.servlet.HandlerInterceptor;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +@Component
    +<span class="nc" id="L12">@Slf4j</span>
    +public class EndpointInterceptor implements HandlerInterceptor {
    +
    +    private final EndpointConfiguration endpointConfiguration;
    +
    +<span class="nc" id="L17">    public EndpointInterceptor(EndpointConfiguration endpointConfiguration) {</span>
    +<span class="nc" id="L18">        this.endpointConfiguration = endpointConfiguration;</span>
    +<span class="nc" id="L19">    }</span>
    +
    +    @Override
    +    public boolean preHandle(
    +            HttpServletRequest request, HttpServletResponse response, Object handler)
    +            throws Exception {
    +<span class="nc" id="L25">        String requestURI = request.getRequestURI();</span>
    +<span class="nc bnc" id="L26" title="All 2 branches missed.">        if (!endpointConfiguration.isEndpointEnabled(requestURI)) {</span>
    +<span class="nc" id="L27">            response.sendError(HttpServletResponse.SC_FORBIDDEN, &quot;This endpoint is disabled&quot;);</span>
    +<span class="nc" id="L28">            return false;</span>
    +        }
    +<span class="nc" id="L30">        return true;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/EnterpriseEndpointFilter.html b/testResult/html/stirling.software.SPDF.config/EnterpriseEndpointFilter.html
    new file mode 100644
    index 000000000..0611e361d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/EnterpriseEndpointFilter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EnterpriseEndpointFilter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">EnterpriseEndpointFilter</span></div><h1>EnterpriseEndpointFilter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">28 of 28</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="EnterpriseEndpointFilter.java.html#L28" class="el_method">doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="17" alt="17"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="EnterpriseEndpointFilter.java.html#L19" class="el_method">EnterpriseEndpointFilter(boolean)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="EnterpriseEndpointFilter.java.html#L36" class="el_method">isPrometheusEndpointRequest(HttpServletRequest)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="5" alt="5"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/EnterpriseEndpointFilter.java.html b/testResult/html/stirling.software.SPDF.config/EnterpriseEndpointFilter.java.html
    new file mode 100644
    index 000000000..3ae806a0d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/EnterpriseEndpointFilter.java.html
    @@ -0,0 +1,39 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>EnterpriseEndpointFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">EnterpriseEndpointFilter.java</span></div><h1>EnterpriseEndpointFilter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.IOException;
    +
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.filter.OncePerRequestFilter;
    +
    +import jakarta.servlet.FilterChain;
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +@Component
    +public class EnterpriseEndpointFilter extends OncePerRequestFilter {
    +    private final boolean runningProOrHigher;
    +
    +<span class="nc" id="L19">    public EnterpriseEndpointFilter(@Qualifier(&quot;runningProOrHigher&quot;) boolean runningProOrHigher) {</span>
    +<span class="nc" id="L20">        this.runningProOrHigher = runningProOrHigher;</span>
    +<span class="nc" id="L21">    }</span>
    +
    +    @Override
    +    protected void doFilterInternal(
    +            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    +            throws ServletException, IOException {
    +
    +<span class="nc bnc" id="L28" title="All 4 branches missed.">        if (!runningProOrHigher &amp;&amp; isPrometheusEndpointRequest(request)) {</span>
    +<span class="nc" id="L29">            response.setStatus(HttpStatus.NOT_FOUND.value());</span>
    +<span class="nc" id="L30">            return;</span>
    +        }
    +<span class="nc" id="L32">        filterChain.doFilter(request, response);</span>
    +<span class="nc" id="L33">    }</span>
    +
    +    private boolean isPrometheusEndpointRequest(HttpServletRequest request) {
    +<span class="nc" id="L36">        return request.getRequestURI().contains(&quot;/actuator/&quot;);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig$1.html b/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig$1.html
    new file mode 100644
    index 000000000..d40afdcae
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExternalAppDepConfig.new HashMap() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">ExternalAppDepConfig.new HashMap() {...}</span></div><h1>ExternalAppDepConfig.new HashMap() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">46 of 46</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ExternalAppDepConfig.java.html#L32" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="46" alt="46"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig.html b/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig.html
    new file mode 100644
    index 000000000..115e40478
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExternalAppDepConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">ExternalAppDepConfig</span></div><h1>ExternalAppDepConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">358 of 358</td><td class="ctr2">0%</td><td class="bar">28 of 28</td><td class="ctr2">0%</td><td class="ctr1">24</td><td class="ctr2">24</td><td class="ctr1">78</td><td class="ctr2">78</td><td class="ctr1">10</td><td class="ctr2">10</td></tr></tfoot><tbody><tr><td id="a1"><a href="ExternalAppDepConfig.java.html#L110" class="el_method">checkDependencies()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="151" alt="151"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">36</td><td class="ctr2" id="i0">36</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a6"><a href="ExternalAppDepConfig.java.html#L47" class="el_method">isCommandAvailable(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="47" height="10" title="60" alt="60"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h2">10</td><td class="ctr2" id="i2">10</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="ExternalAppDepConfig.java.html#L88" class="el_method">checkDependencyAndDisableGroup(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="47" height="10" title="60" alt="60"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h1">13</td><td class="ctr2" id="i1">13</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="ExternalAppDepConfig.java.html#L70" class="el_method">formatEndpointAsFeature(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="23" alt="23"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h5">4</td><td class="ctr2" id="i5">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="ExternalAppDepConfig.java.html#L78" class="el_method">capitalizeWord(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="22" alt="22"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h4">5</td><td class="ctr2" id="i4">5</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="ExternalAppDepConfig.java.html#L26" class="el_method">ExternalAppDepConfig(EndpointConfiguration, RuntimePathConfig)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="20" alt="20"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h3">6</td><td class="ctr2" id="i3">6</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a5"><a href="ExternalAppDepConfig.java.html#L63" class="el_method">getAffectedFeatures(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="10" alt="10"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a7"><a href="ExternalAppDepConfig.java.html#L73" class="el_method">lambda$formatEndpointAsFeature$1(String)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="ExternalAppDepConfig.java.html#L64" class="el_method">lambda$getAffectedFeatures$0(String)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a9"><a href="ExternalAppDepConfig.java.html#L16" class="el_method">static {...}</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig.java.html b/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig.java.html
    new file mode 100644
    index 000000000..ed1c6c3e9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/ExternalAppDepConfig.java.html
    @@ -0,0 +1,157 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExternalAppDepConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">ExternalAppDepConfig.java</span></div><h1>ExternalAppDepConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.util.Arrays;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.stream.Collectors;
    +
    +import org.springframework.context.annotation.Configuration;
    +
    +import jakarta.annotation.PostConstruct;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +@Configuration
    +<span class="nc" id="L16">@Slf4j</span>
    +public class ExternalAppDepConfig {
    +
    +    private final EndpointConfiguration endpointConfiguration;
    +
    +    private final String weasyprintPath;
    +    private final String unoconvPath;
    +    private final Map&lt;String, List&lt;String&gt;&gt; commandToGroupMapping;
    +
    +    public ExternalAppDepConfig(
    +<span class="nc" id="L26">            EndpointConfiguration endpointConfiguration, RuntimePathConfig runtimePathConfig) {</span>
    +<span class="nc" id="L27">        this.endpointConfiguration = endpointConfiguration;</span>
    +<span class="nc" id="L28">        weasyprintPath = runtimePathConfig.getWeasyPrintPath();</span>
    +<span class="nc" id="L29">        unoconvPath = runtimePathConfig.getUnoConvertPath();</span>
    +
    +<span class="nc" id="L31">        commandToGroupMapping =</span>
    +<span class="nc" id="L32">                new HashMap&lt;&gt;() {</span>
    +
    +                    {
    +<span class="nc" id="L35">                        put(&quot;soffice&quot;, List.of(&quot;LibreOffice&quot;));</span>
    +<span class="nc" id="L36">                        put(weasyprintPath, List.of(&quot;Weasyprint&quot;));</span>
    +<span class="nc" id="L37">                        put(&quot;pdftohtml&quot;, List.of(&quot;Pdftohtml&quot;));</span>
    +<span class="nc" id="L38">                        put(unoconvPath, List.of(&quot;Unoconvert&quot;));</span>
    +<span class="nc" id="L39">                        put(&quot;qpdf&quot;, List.of(&quot;qpdf&quot;));</span>
    +<span class="nc" id="L40">                        put(&quot;tesseract&quot;, List.of(&quot;tesseract&quot;));</span>
    +                    }
    +                };
    +<span class="nc" id="L43">    }</span>
    +
    +    private boolean isCommandAvailable(String command) {
    +        try {
    +<span class="nc" id="L47">            ProcessBuilder processBuilder = new ProcessBuilder();</span>
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">            if (System.getProperty(&quot;os.name&quot;).toLowerCase().contains(&quot;windows&quot;)) {</span>
    +<span class="nc" id="L49">                processBuilder.command(&quot;where&quot;, command);</span>
    +            } else {
    +<span class="nc" id="L51">                processBuilder.command(&quot;which&quot;, command);</span>
    +            }
    +<span class="nc" id="L53">            Process process = processBuilder.start();</span>
    +<span class="nc" id="L54">            int exitCode = process.waitFor();</span>
    +<span class="nc bnc" id="L55" title="All 2 branches missed.">            return exitCode == 0;</span>
    +<span class="nc" id="L56">        } catch (Exception e) {</span>
    +<span class="nc" id="L57">            log.debug(&quot;Error checking for command {}: {}&quot;, command, e.getMessage());</span>
    +<span class="nc" id="L58">            return false;</span>
    +        }
    +    }
    +
    +    private List&lt;String&gt; getAffectedFeatures(String group) {
    +<span class="nc" id="L63">        return endpointConfiguration.getEndpointsForGroup(group).stream()</span>
    +<span class="nc" id="L64">                .map(endpoint -&gt; formatEndpointAsFeature(endpoint))</span>
    +<span class="nc" id="L65">                .toList();</span>
    +    }
    +
    +    private String formatEndpointAsFeature(String endpoint) {
    +        // First replace common terms
    +<span class="nc" id="L70">        String feature = endpoint.replace(&quot;-&quot;, &quot; &quot;).replace(&quot;pdf&quot;, &quot;PDF&quot;).replace(&quot;img&quot;, &quot;image&quot;);</span>
    +        // Split into words and capitalize each word
    +<span class="nc" id="L72">        return Arrays.stream(feature.split(&quot;\\s+&quot;))</span>
    +<span class="nc" id="L73">                .map(word -&gt; capitalizeWord(word))</span>
    +<span class="nc" id="L74">                .collect(Collectors.joining(&quot; &quot;));</span>
    +    }
    +
    +    private String capitalizeWord(String word) {
    +<span class="nc bnc" id="L78" title="All 2 branches missed.">        if (word.isEmpty()) {</span>
    +<span class="nc" id="L79">            return word;</span>
    +        }
    +<span class="nc bnc" id="L81" title="All 2 branches missed.">        if (&quot;pdf&quot;.equalsIgnoreCase(word)) {</span>
    +<span class="nc" id="L82">            return &quot;PDF&quot;;</span>
    +        }
    +<span class="nc" id="L84">        return word.substring(0, 1).toUpperCase() + word.substring(1).toLowerCase();</span>
    +    }
    +
    +    private void checkDependencyAndDisableGroup(String command) {
    +<span class="nc" id="L88">        boolean isAvailable = isCommandAvailable(command);</span>
    +<span class="nc bnc" id="L89" title="All 2 branches missed.">        if (!isAvailable) {</span>
    +<span class="nc" id="L90">            List&lt;String&gt; affectedGroups = commandToGroupMapping.get(command);</span>
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">            if (affectedGroups != null) {</span>
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">                for (String group : affectedGroups) {</span>
    +<span class="nc" id="L93">                    List&lt;String&gt; affectedFeatures = getAffectedFeatures(group);</span>
    +<span class="nc" id="L94">                    endpointConfiguration.disableGroup(group);</span>
    +<span class="nc" id="L95">                    log.warn(</span>
    +                            &quot;Missing dependency: {} - Disabling group: {} (Affected features: {})&quot;,
    +                            command,
    +                            group,
    +<span class="nc bnc" id="L99" title="All 4 branches missed.">                            affectedFeatures != null &amp;&amp; !affectedFeatures.isEmpty()</span>
    +<span class="nc" id="L100">                                    ? String.join(&quot;, &quot;, affectedFeatures)</span>
    +<span class="nc" id="L101">                                    : &quot;unknown&quot;);</span>
    +<span class="nc" id="L102">                }</span>
    +            }
    +        }
    +<span class="nc" id="L105">    }</span>
    +
    +    @PostConstruct
    +    public void checkDependencies() {
    +        // Check core dependencies
    +<span class="nc" id="L110">        checkDependencyAndDisableGroup(&quot;tesseract&quot;);</span>
    +<span class="nc" id="L111">        checkDependencyAndDisableGroup(&quot;soffice&quot;);</span>
    +<span class="nc" id="L112">        checkDependencyAndDisableGroup(&quot;qpdf&quot;);</span>
    +<span class="nc" id="L113">        checkDependencyAndDisableGroup(weasyprintPath);</span>
    +<span class="nc" id="L114">        checkDependencyAndDisableGroup(&quot;pdftohtml&quot;);</span>
    +<span class="nc" id="L115">        checkDependencyAndDisableGroup(unoconvPath);</span>
    +        // Special handling for Python/OpenCV dependencies
    +<span class="nc bnc" id="L117" title="All 4 branches missed.">        boolean pythonAvailable = isCommandAvailable(&quot;python3&quot;) || isCommandAvailable(&quot;python&quot;);</span>
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">        if (!pythonAvailable) {</span>
    +<span class="nc" id="L119">            List&lt;String&gt; pythonFeatures = getAffectedFeatures(&quot;Python&quot;);</span>
    +<span class="nc" id="L120">            List&lt;String&gt; openCVFeatures = getAffectedFeatures(&quot;OpenCV&quot;);</span>
    +<span class="nc" id="L121">            endpointConfiguration.disableGroup(&quot;Python&quot;);</span>
    +<span class="nc" id="L122">            endpointConfiguration.disableGroup(&quot;OpenCV&quot;);</span>
    +<span class="nc" id="L123">            log.warn(</span>
    +                    &quot;Missing dependency: Python - Disabling Python features: {} and OpenCV features: {}&quot;,
    +<span class="nc" id="L125">                    String.join(&quot;, &quot;, pythonFeatures),</span>
    +<span class="nc" id="L126">                    String.join(&quot;, &quot;, openCVFeatures));</span>
    +<span class="nc" id="L127">        } else {</span>
    +            // If Python is available, check for OpenCV
    +            try {
    +<span class="nc" id="L130">                ProcessBuilder processBuilder = new ProcessBuilder();</span>
    +<span class="nc bnc" id="L131" title="All 2 branches missed.">                if (System.getProperty(&quot;os.name&quot;).toLowerCase().contains(&quot;windows&quot;)) {</span>
    +<span class="nc" id="L132">                    processBuilder.command(&quot;python&quot;, &quot;-c&quot;, &quot;import cv2&quot;);</span>
    +                } else {
    +<span class="nc" id="L134">                    processBuilder.command(&quot;python3&quot;, &quot;-c&quot;, &quot;import cv2&quot;);</span>
    +                }
    +<span class="nc" id="L136">                Process process = processBuilder.start();</span>
    +<span class="nc" id="L137">                int exitCode = process.waitFor();</span>
    +<span class="nc bnc" id="L138" title="All 2 branches missed.">                if (exitCode != 0) {</span>
    +<span class="nc" id="L139">                    List&lt;String&gt; openCVFeatures = getAffectedFeatures(&quot;OpenCV&quot;);</span>
    +<span class="nc" id="L140">                    endpointConfiguration.disableGroup(&quot;OpenCV&quot;);</span>
    +<span class="nc" id="L141">                    log.warn(</span>
    +                            &quot;OpenCV not available in Python - Disabling OpenCV features: {}&quot;,
    +<span class="nc" id="L143">                            String.join(&quot;, &quot;, openCVFeatures));</span>
    +                }
    +<span class="nc" id="L145">            } catch (Exception e) {</span>
    +<span class="nc" id="L146">                List&lt;String&gt; openCVFeatures = getAffectedFeatures(&quot;OpenCV&quot;);</span>
    +<span class="nc" id="L147">                endpointConfiguration.disableGroup(&quot;OpenCV&quot;);</span>
    +<span class="nc" id="L148">                log.warn(</span>
    +                        &quot;Error checking OpenCV: {} - Disabling OpenCV features: {}&quot;,
    +<span class="nc" id="L150">                        e.getMessage(),</span>
    +<span class="nc" id="L151">                        String.join(&quot;, &quot;, openCVFeatures));</span>
    +<span class="nc" id="L152">            }</span>
    +        }
    +<span class="nc" id="L154">        endpointConfiguration.logDisabledEndpointsSummary();</span>
    +<span class="nc" id="L155">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/FileFallbackTemplateResolver.html b/testResult/html/stirling.software.SPDF.config/FileFallbackTemplateResolver.html
    new file mode 100644
    index 000000000..0d17aa8ae
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/FileFallbackTemplateResolver.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileFallbackTemplateResolver</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">FileFallbackTemplateResolver</span></div><h1>FileFallbackTemplateResolver</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">48 of 48</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">17</td><td class="ctr2">17</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="FileFallbackTemplateResolver.java.html#L35" class="el_method">computeTemplateResource(IEngineConfiguration, String, String, String, String, Map)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="39" alt="39"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">13</td><td class="ctr2" id="i0">13</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="FileFallbackTemplateResolver.java.html#L21" class="el_method">FileFallbackTemplateResolver(ResourceLoader)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/FileFallbackTemplateResolver.java.html b/testResult/html/stirling.software.SPDF.config/FileFallbackTemplateResolver.java.html
    new file mode 100644
    index 000000000..db524e248
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/FileFallbackTemplateResolver.java.html
    @@ -0,0 +1,56 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileFallbackTemplateResolver.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">FileFallbackTemplateResolver.java</span></div><h1>FileFallbackTemplateResolver.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.util.Map;
    +
    +import org.springframework.core.io.Resource;
    +import org.springframework.core.io.ResourceLoader;
    +import org.thymeleaf.IEngineConfiguration;
    +import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver;
    +import org.thymeleaf.templateresource.FileTemplateResource;
    +import org.thymeleaf.templateresource.ITemplateResource;
    +
    +import stirling.software.SPDF.model.InputStreamTemplateResource;
    +
    +public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver {
    +
    +    private final ResourceLoader resourceLoader;
    +
    +    public FileFallbackTemplateResolver(ResourceLoader resourceLoader) {
    +<span class="nc" id="L21">        super();</span>
    +<span class="nc" id="L22">        this.resourceLoader = resourceLoader;</span>
    +<span class="nc" id="L23">        setSuffix(&quot;.html&quot;);</span>
    +<span class="nc" id="L24">    }</span>
    +
    +    // Note this does not work in local IDE, Prod jar only.
    +    @Override
    +    protected ITemplateResource computeTemplateResource(
    +            IEngineConfiguration configuration,
    +            String ownerTemplate,
    +            String template,
    +            String resourceName,
    +            String characterEncoding,
    +            Map&lt;String, Object&gt; templateResolutionAttributes) {
    +<span class="nc" id="L35">        Resource resource =</span>
    +<span class="nc" id="L36">                resourceLoader.getResource(</span>
    +<span class="nc" id="L37">                        &quot;file:&quot; + InstallationPathConfig.getTemplatesPath() + resourceName);</span>
    +        try {
    +<span class="nc bnc" id="L39" title="All 4 branches missed.">            if (resource.exists() &amp;&amp; resource.isReadable()) {</span>
    +<span class="nc" id="L40">                return new FileTemplateResource(resource.getFile().getPath(), characterEncoding);</span>
    +            }
    +<span class="nc" id="L42">        } catch (IOException e) {</span>
    +
    +<span class="nc" id="L44">        }</span>
    +
    +        InputStream inputStream =
    +<span class="nc" id="L47">                Thread.currentThread()</span>
    +<span class="nc" id="L48">                        .getContextClassLoader()</span>
    +<span class="nc" id="L49">                        .getResourceAsStream(&quot;templates/&quot; + resourceName);</span>
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">        if (inputStream != null) {</span>
    +<span class="nc" id="L51">            return new InputStreamTemplateResource(inputStream, &quot;UTF-8&quot;);</span>
    +        }
    +<span class="nc" id="L53">        return null;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/InitialSetup.html b/testResult/html/stirling.software.SPDF.config/InitialSetup.html
    new file mode 100644
    index 000000000..78925cc2d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/InitialSetup.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InitialSetup</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">InitialSetup</span></div><h1>InitialSetup</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">158 of 158</td><td class="ctr2">0%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">51</td><td class="ctr2">51</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a3"><a href="InitialSetup.java.html#L76" class="el_method">initLegalUrls()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="37" alt="37"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">11</td><td class="ctr2" id="i0">11</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="InitialSetup.java.html#L63" class="el_method">initEnableCSRFSecurity()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="97" height="10" title="30" alt="30"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h2">8</td><td class="ctr2" id="i2">8</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="InitialSetup.java.html#L92" class="el_method">initSetAppVersion()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="97" height="10" title="30" alt="30"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h1">10</td><td class="ctr2" id="i1">10</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="InitialSetup.java.html#L43" class="el_method">initUUIDKey()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="20" alt="20"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h3">6</td><td class="ctr2" id="i3">6</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="InitialSetup.java.html#L53" class="el_method">initSecretKey()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="20" alt="20"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h4">6</td><td class="ctr2" id="i4">6</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="InitialSetup.java.html#L35" class="el_method">init()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="11" alt="11"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="InitialSetup.java.html#L29" class="el_method">InitialSetup(ApplicationProperties)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="6" alt="6"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a7"><a href="InitialSetup.java.html#L23" class="el_method">static {...}</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="4" alt="4"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/InitialSetup.java.html b/testResult/html/stirling.software.SPDF.config/InitialSetup.java.html
    new file mode 100644
    index 000000000..1bc61c828
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/InitialSetup.java.html
    @@ -0,0 +1,104 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InitialSetup.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">InitialSetup.java</span></div><h1>InitialSetup.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.IOException;
    +import java.util.Properties;
    +import java.util.UUID;
    +
    +import org.springframework.core.Ordered;
    +import org.springframework.core.annotation.Order;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.core.io.Resource;
    +import org.springframework.stereotype.Component;
    +
    +import io.micrometer.common.util.StringUtils;
    +
    +import jakarta.annotation.PostConstruct;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +
    +@Component
    +<span class="nc" id="L23">@Slf4j</span>
    +@Order(Ordered.HIGHEST_PRECEDENCE + 1)
    +public class InitialSetup {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L29">    public InitialSetup(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L30">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L31">    }</span>
    +
    +    @PostConstruct
    +    public void init() throws IOException {
    +<span class="nc" id="L35">        initUUIDKey();</span>
    +<span class="nc" id="L36">        initSecretKey();</span>
    +<span class="nc" id="L37">        initEnableCSRFSecurity();</span>
    +<span class="nc" id="L38">        initLegalUrls();</span>
    +<span class="nc" id="L39">        initSetAppVersion();</span>
    +<span class="nc" id="L40">    }</span>
    +
    +    public void initUUIDKey() throws IOException {
    +<span class="nc" id="L43">        String uuid = applicationProperties.getAutomaticallyGenerated().getUUID();</span>
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">        if (!GeneralUtils.isValidUUID(uuid)) {</span>
    +            // Generating a random UUID as the secret key
    +<span class="nc" id="L46">            uuid = UUID.randomUUID().toString();</span>
    +<span class="nc" id="L47">            GeneralUtils.saveKeyToSettings(&quot;AutomaticallyGenerated.UUID&quot;, uuid);</span>
    +<span class="nc" id="L48">            applicationProperties.getAutomaticallyGenerated().setUUID(uuid);</span>
    +        }
    +<span class="nc" id="L50">    }</span>
    +
    +    public void initSecretKey() throws IOException {
    +<span class="nc" id="L53">        String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();</span>
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">        if (!GeneralUtils.isValidUUID(secretKey)) {</span>
    +            // Generating a random UUID as the secret key
    +<span class="nc" id="L56">            secretKey = UUID.randomUUID().toString();</span>
    +<span class="nc" id="L57">            GeneralUtils.saveKeyToSettings(&quot;AutomaticallyGenerated.key&quot;, secretKey);</span>
    +<span class="nc" id="L58">            applicationProperties.getAutomaticallyGenerated().setKey(secretKey);</span>
    +        }
    +<span class="nc" id="L60">    }</span>
    +
    +    public void initEnableCSRFSecurity() throws IOException {
    +<span class="nc bnc" id="L63" title="All 2 branches missed.">        if (GeneralUtils.isVersionHigher(</span>
    +<span class="nc" id="L64">                &quot;0.36.0&quot;, applicationProperties.getAutomaticallyGenerated().getAppVersion())) {</span>
    +<span class="nc" id="L65">            Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled();</span>
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">            if (!csrf) {</span>
    +<span class="nc" id="L67">                GeneralUtils.saveKeyToSettings(&quot;security.csrfDisabled&quot;, false);</span>
    +<span class="nc" id="L68">                GeneralUtils.saveKeyToSettings(&quot;system.enableAnalytics&quot;, true);</span>
    +<span class="nc" id="L69">                applicationProperties.getSecurity().setCsrfDisabled(false);</span>
    +            }
    +        }
    +<span class="nc" id="L72">    }</span>
    +
    +    public void initLegalUrls() throws IOException {
    +        // Initialize Terms and Conditions
    +<span class="nc" id="L76">        String termsUrl = applicationProperties.getLegal().getTermsAndConditions();</span>
    +<span class="nc bnc" id="L77" title="All 2 branches missed.">        if (StringUtils.isEmpty(termsUrl)) {</span>
    +<span class="nc" id="L78">            String defaultTermsUrl = &quot;https://www.stirlingpdf.com/terms-and-conditions&quot;;</span>
    +<span class="nc" id="L79">            GeneralUtils.saveKeyToSettings(&quot;legal.termsAndConditions&quot;, defaultTermsUrl);</span>
    +<span class="nc" id="L80">            applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);</span>
    +        }
    +        // Initialize Privacy Policy
    +<span class="nc" id="L83">        String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();</span>
    +<span class="nc bnc" id="L84" title="All 2 branches missed.">        if (StringUtils.isEmpty(privacyUrl)) {</span>
    +<span class="nc" id="L85">            String defaultPrivacyUrl = &quot;https://www.stirlingpdf.com/privacy-policy&quot;;</span>
    +<span class="nc" id="L86">            GeneralUtils.saveKeyToSettings(&quot;legal.privacyPolicy&quot;, defaultPrivacyUrl);</span>
    +<span class="nc" id="L87">            applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);</span>
    +        }
    +<span class="nc" id="L89">    }</span>
    +
    +    public void initSetAppVersion() throws IOException {
    +<span class="nc" id="L92">        String appVersion = &quot;0.0.0&quot;;</span>
    +<span class="nc" id="L93">        Resource resource = new ClassPathResource(&quot;version.properties&quot;);</span>
    +<span class="nc" id="L94">        Properties props = new Properties();</span>
    +        try {
    +<span class="nc" id="L96">            props.load(resource.getInputStream());</span>
    +<span class="nc" id="L97">            appVersion = props.getProperty(&quot;version&quot;);</span>
    +<span class="nc" id="L98">        } catch (Exception e) {</span>
    +<span class="nc" id="L99">        }</span>
    +<span class="nc" id="L100">        GeneralUtils.saveKeyToSettings(&quot;AutomaticallyGenerated.appVersion&quot;, appVersion);</span>
    +<span class="nc" id="L101">        applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion);</span>
    +<span class="nc" id="L102">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/InstallationPathConfig.html b/testResult/html/stirling.software.SPDF.config/InstallationPathConfig.html
    new file mode 100644
    index 000000000..07eb71ea0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/InstallationPathConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InstallationPathConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">InstallationPathConfig</span></div><h1>InstallationPathConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">84 of 134</td><td class="ctr2">37%</td><td class="bar">5 of 6</td><td class="ctr2">16%</td><td class="ctr1">13</td><td class="ctr2">16</td><td class="ctr1">22</td><td class="ctr2">37</td><td class="ctr1">10</td><td class="ctr2">13</td></tr></tfoot><tbody><tr><td id="a10"><a href="InstallationPathConfig.java.html#L47" class="el_method">initializeBasePath()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="106" height="10" title="63" alt="63"/><img src="../jacoco-resources/greenbar.gif" width="13" height="10" title="8" alt="8"/></td><td class="ctr2" id="c2">11%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="5" alt="5"/><img src="../jacoco-resources/greenbar.gif" width="20" height="10" title="1" alt="1"/></td><td class="ctr2" id="e0">16%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">14</td><td class="ctr1" id="j10">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a11"><a href="InstallationPathConfig.java.html#L9" class="el_method">InstallationPathConfig()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="InstallationPathConfig.java.html#L76" class="el_method">getPath()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="InstallationPathConfig.java.html#L84" class="el_method">getConfigPath()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="InstallationPathConfig.java.html#L88" class="el_method">getCustomFilesPath()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="InstallationPathConfig.java.html#L92" class="el_method">getClientWebUIPath()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="InstallationPathConfig.java.html#L96" class="el_method">getSettingsPath()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a3"><a href="InstallationPathConfig.java.html#L100" class="el_method">getCustomSettingsPath()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="InstallationPathConfig.java.html#L104" class="el_method">getStaticPath()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a9"><a href="InstallationPathConfig.java.html#L108" class="el_method">getTemplatesPath()</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a7"><a href="InstallationPathConfig.java.html#L112" class="el_method">getSignaturesPath()</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i11">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a12"><a href="InstallationPathConfig.java.html#L8" class="el_method">static {...}</a></td><td class="bar" id="b11"><img src="../jacoco-resources/greenbar.gif" width="67" height="10" title="40" alt="40"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">0</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">0</td><td class="ctr2" id="i1">12</td><td class="ctr1" id="j11">0</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a4"><a href="InstallationPathConfig.java.html#L80" class="el_method">getLogPath()</a></td><td class="bar" id="b12"><img src="../jacoco-resources/greenbar.gif" width="3" height="10" title="2" alt="2"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">0</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h12">0</td><td class="ctr2" id="i12">1</td><td class="ctr1" id="j12">0</td><td class="ctr2" id="k12">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/InstallationPathConfig.java.html b/testResult/html/stirling.software.SPDF.config/InstallationPathConfig.java.html
    new file mode 100644
    index 000000000..fbd6582f8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/InstallationPathConfig.java.html
    @@ -0,0 +1,115 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InstallationPathConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">InstallationPathConfig.java</span></div><h1>InstallationPathConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.File;
    +import java.nio.file.Paths;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +<span class="fc" id="L8">@Slf4j</span>
    +<span class="nc" id="L9">public class InstallationPathConfig {</span>
    +    private static final String BASE_PATH;
    +
    +    // Root paths
    +    private static final String LOG_PATH;
    +    private static final String CONFIG_PATH;
    +    private static final String CUSTOM_FILES_PATH;
    +    private static final String CLIENT_WEBUI_PATH;
    +
    +    // Config paths
    +    private static final String SETTINGS_PATH;
    +    private static final String CUSTOM_SETTINGS_PATH;
    +
    +    // Custom file paths
    +    private static final String STATIC_PATH;
    +    private static final String TEMPLATES_PATH;
    +    private static final String SIGNATURES_PATH;
    +
    +    static {
    +<span class="fc" id="L28">        BASE_PATH = initializeBasePath();</span>
    +
    +        // Initialize root paths
    +<span class="fc" id="L31">        LOG_PATH = BASE_PATH + &quot;logs&quot; + File.separator;</span>
    +<span class="fc" id="L32">        CONFIG_PATH = BASE_PATH + &quot;configs&quot; + File.separator;</span>
    +<span class="fc" id="L33">        CUSTOM_FILES_PATH = BASE_PATH + &quot;customFiles&quot; + File.separator;</span>
    +<span class="fc" id="L34">        CLIENT_WEBUI_PATH = BASE_PATH + &quot;clientWebUI&quot; + File.separator;</span>
    +
    +        // Initialize config paths
    +<span class="fc" id="L37">        SETTINGS_PATH = CONFIG_PATH + &quot;settings.yml&quot;;</span>
    +<span class="fc" id="L38">        CUSTOM_SETTINGS_PATH = CONFIG_PATH + &quot;custom_settings.yml&quot;;</span>
    +
    +        // Initialize custom file paths
    +<span class="fc" id="L41">        STATIC_PATH = CUSTOM_FILES_PATH + &quot;static&quot; + File.separator;</span>
    +<span class="fc" id="L42">        TEMPLATES_PATH = CUSTOM_FILES_PATH + &quot;templates&quot; + File.separator;</span>
    +<span class="fc" id="L43">        SIGNATURES_PATH = CUSTOM_FILES_PATH + &quot;signatures&quot; + File.separator;</span>
    +<span class="fc" id="L44">    }</span>
    +
    +    private static String initializeBasePath() {
    +<span class="pc bpc" id="L47" title="1 of 2 branches missed.">        if (Boolean.parseBoolean(System.getProperty(&quot;STIRLING_PDF_DESKTOP_UI&quot;, &quot;false&quot;))) {</span>
    +<span class="nc" id="L48">            String os = System.getProperty(&quot;os.name&quot;).toLowerCase();</span>
    +<span class="nc bnc" id="L49" title="All 2 branches missed.">            if (os.contains(&quot;win&quot;)) {</span>
    +<span class="nc" id="L50">                return Paths.get(</span>
    +<span class="nc" id="L51">                                        System.getenv(&quot;APPDATA&quot;), // parent path</span>
    +                                        &quot;Stirling-PDF&quot;)
    +<span class="nc" id="L53">                                .toString()</span>
    +                        + File.separator;
    +<span class="nc bnc" id="L55" title="All 2 branches missed.">            } else if (os.contains(&quot;mac&quot;)) {</span>
    +<span class="nc" id="L56">                return Paths.get(</span>
    +<span class="nc" id="L57">                                        System.getProperty(&quot;user.home&quot;),</span>
    +                                        &quot;Library&quot;,
    +                                        &quot;Application Support&quot;,
    +                                        &quot;Stirling-PDF&quot;)
    +<span class="nc" id="L61">                                .toString()</span>
    +                        + File.separator;
    +            } else {
    +<span class="nc" id="L64">                return Paths.get(</span>
    +<span class="nc" id="L65">                                        System.getProperty(&quot;user.home&quot;), // parent path</span>
    +                                        &quot;.config&quot;,
    +                                        &quot;Stirling-PDF&quot;)
    +<span class="nc" id="L68">                                .toString()</span>
    +                        + File.separator;
    +            }
    +        }
    +<span class="fc" id="L72">        return &quot;.&quot; + File.separator;</span>
    +    }
    +
    +    public static String getPath() {
    +<span class="nc" id="L76">        return BASE_PATH;</span>
    +    }
    +
    +    public static String getLogPath() {
    +<span class="fc" id="L80">        return LOG_PATH;</span>
    +    }
    +
    +    public static String getConfigPath() {
    +<span class="nc" id="L84">        return CONFIG_PATH;</span>
    +    }
    +
    +    public static String getCustomFilesPath() {
    +<span class="nc" id="L88">        return CUSTOM_FILES_PATH;</span>
    +    }
    +
    +    public static String getClientWebUIPath() {
    +<span class="nc" id="L92">        return CLIENT_WEBUI_PATH;</span>
    +    }
    +
    +    public static String getSettingsPath() {
    +<span class="nc" id="L96">        return SETTINGS_PATH;</span>
    +    }
    +
    +    public static String getCustomSettingsPath() {
    +<span class="nc" id="L100">        return CUSTOM_SETTINGS_PATH;</span>
    +    }
    +
    +    public static String getStaticPath() {
    +<span class="nc" id="L104">        return STATIC_PATH;</span>
    +    }
    +
    +    public static String getTemplatesPath() {
    +<span class="nc" id="L108">        return TEMPLATES_PATH;</span>
    +    }
    +
    +    public static String getSignaturesPath() {
    +<span class="nc" id="L112">        return SIGNATURES_PATH;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/LocaleConfiguration.html b/testResult/html/stirling.software.SPDF.config/LocaleConfiguration.html
    new file mode 100644
    index 000000000..175854046
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/LocaleConfiguration.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LocaleConfiguration</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">LocaleConfiguration</span></div><h1>LocaleConfiguration</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">80 of 80</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">24</td><td class="ctr2">24</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a3"><a href="LocaleConfiguration.java.html#L39" class="el_method">localeResolver()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="53" alt="53"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">15</td><td class="ctr2" id="i0">15</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="LocaleConfiguration.java.html#L26" class="el_method">addInterceptors(InterceptorRegistry)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="LocaleConfiguration.java.html#L32" class="el_method">localeChangeInterceptor()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="9" alt="9"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="LocaleConfiguration.java.html#L20" class="el_method">LocaleConfiguration(ApplicationProperties)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/LocaleConfiguration.java.html b/testResult/html/stirling.software.SPDF.config/LocaleConfiguration.java.html
    new file mode 100644
    index 000000000..b7c6a3f31
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/LocaleConfiguration.java.html
    @@ -0,0 +1,63 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LocaleConfiguration.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">LocaleConfiguration.java</span></div><h1>LocaleConfiguration.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.util.Locale;
    +
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.web.servlet.LocaleResolver;
    +import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
    +import org.springframework.web.servlet.i18n.SessionLocaleResolver;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +@Configuration
    +public class LocaleConfiguration implements WebMvcConfigurer {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L20">    public LocaleConfiguration(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L21">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L22">    }</span>
    +
    +    @Override
    +    public void addInterceptors(InterceptorRegistry registry) {
    +<span class="nc" id="L26">        registry.addInterceptor(localeChangeInterceptor());</span>
    +<span class="nc" id="L27">        registry.addInterceptor(new CleanUrlInterceptor());</span>
    +<span class="nc" id="L28">    }</span>
    +
    +    @Bean
    +    public LocaleChangeInterceptor localeChangeInterceptor() {
    +<span class="nc" id="L32">        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();</span>
    +<span class="nc" id="L33">        lci.setParamName(&quot;lang&quot;);</span>
    +<span class="nc" id="L34">        return lci;</span>
    +    }
    +
    +    @Bean
    +    public LocaleResolver localeResolver() {
    +<span class="nc" id="L39">        SessionLocaleResolver slr = new SessionLocaleResolver();</span>
    +<span class="nc" id="L40">        String appLocaleEnv = applicationProperties.getSystem().getDefaultLocale();</span>
    +<span class="nc" id="L41">        Locale defaultLocale = // Fallback to UK locale if environment variable is not set</span>
    +                Locale.UK;
    +<span class="nc bnc" id="L43" title="All 4 branches missed.">        if (appLocaleEnv != null &amp;&amp; !appLocaleEnv.isEmpty()) {</span>
    +<span class="nc" id="L44">            Locale tempLocale = Locale.forLanguageTag(appLocaleEnv);</span>
    +<span class="nc" id="L45">            String tempLanguageTag = tempLocale.toLanguageTag();</span>
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">            if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {</span>
    +<span class="nc" id="L47">                defaultLocale = tempLocale;</span>
    +            } else {
    +<span class="nc" id="L49">                tempLocale = Locale.forLanguageTag(appLocaleEnv.replace(&quot;_&quot;, &quot;-&quot;));</span>
    +<span class="nc" id="L50">                tempLanguageTag = tempLocale.toLanguageTag();</span>
    +<span class="nc bnc" id="L51" title="All 2 branches missed.">                if (appLocaleEnv.equalsIgnoreCase(tempLanguageTag)) {</span>
    +<span class="nc" id="L52">                    defaultLocale = tempLocale;</span>
    +                } else {
    +<span class="nc" id="L54">                    System.err.println(</span>
    +                            &quot;Invalid APP_LOCALE environment variable value. Falling back to default Locale.UK.&quot;);
    +                }
    +            }
    +        }
    +<span class="nc" id="L59">        slr.setDefaultLocale(defaultLocale);</span>
    +<span class="nc" id="L60">        return slr;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/LogbackPropertyLoader.html b/testResult/html/stirling.software.SPDF.config/LogbackPropertyLoader.html
    new file mode 100644
    index 000000000..0247086b2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/LogbackPropertyLoader.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LogbackPropertyLoader</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">LogbackPropertyLoader</span></div><h1>LogbackPropertyLoader</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 5</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="LogbackPropertyLoader.java.html#L5" class="el_method">LogbackPropertyLoader()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="3" alt="3"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="LogbackPropertyLoader.java.html#L8" class="el_method">getPropertyValue()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="80" height="10" title="2" alt="2"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/LogbackPropertyLoader.java.html b/testResult/html/stirling.software.SPDF.config/LogbackPropertyLoader.java.html
    new file mode 100644
    index 000000000..d4bf6780e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/LogbackPropertyLoader.java.html
    @@ -0,0 +1,11 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LogbackPropertyLoader.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">LogbackPropertyLoader.java</span></div><h1>LogbackPropertyLoader.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import ch.qos.logback.core.PropertyDefinerBase;
    +
    +<span class="fc" id="L5">public class LogbackPropertyLoader extends PropertyDefinerBase {</span>
    +    @Override
    +    public String getPropertyValue() {
    +<span class="fc" id="L8">        return InstallationPathConfig.getLogPath();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/MetricsConfig$1.html b/testResult/html/stirling.software.SPDF.config/MetricsConfig$1.html
    new file mode 100644
    index 000000000..1447167fa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/MetricsConfig$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsConfig.new MeterFilter() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">MetricsConfig.new MeterFilter() {...}</span></div><h1>MetricsConfig.new MeterFilter() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">15 of 15</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="MetricsConfig.java.html#L18" class="el_method">accept(Meter.Id)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="MetricsConfig.java.html#L15" class="el_method">{...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/MetricsConfig.html b/testResult/html/stirling.software.SPDF.config/MetricsConfig.html
    new file mode 100644
    index 000000000..b46c570c1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/MetricsConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">MetricsConfig</span></div><h1>MetricsConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="MetricsConfig.java.html#L15" class="el_method">meterFilter()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="5" alt="5"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="MetricsConfig.java.html#L11" class="el_method">MetricsConfig()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/MetricsConfig.java.html b/testResult/html/stirling.software.SPDF.config/MetricsConfig.java.html
    new file mode 100644
    index 000000000..6780f7f7c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/MetricsConfig.java.html
    @@ -0,0 +1,26 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">MetricsConfig.java</span></div><h1>MetricsConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +
    +import io.micrometer.core.instrument.Meter;
    +import io.micrometer.core.instrument.config.MeterFilter;
    +import io.micrometer.core.instrument.config.MeterFilterReply;
    +
    +@Configuration
    +<span class="nc" id="L11">public class MetricsConfig {</span>
    +
    +    @Bean
    +    public MeterFilter meterFilter() {
    +<span class="nc" id="L15">        return new MeterFilter() {</span>
    +            @Override
    +            public MeterFilterReply accept(Meter.Id id) {
    +<span class="nc bnc" id="L18" title="All 2 branches missed.">                if (id.getName().equals(&quot;http.requests&quot;)) {</span>
    +<span class="nc" id="L19">                    return MeterFilterReply.NEUTRAL;</span>
    +                }
    +<span class="nc" id="L21">                return MeterFilterReply.DENY;</span>
    +            }
    +        };
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/MetricsFilter.html b/testResult/html/stirling.software.SPDF.config/MetricsFilter.html
    new file mode 100644
    index 000000000..e33c7edab
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/MetricsFilter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsFilter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">MetricsFilter</span></div><h1>MetricsFilter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">48 of 48</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">16</td><td class="ctr2">16</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="MetricsFilter.java.html#L34" class="el_method">doFilterInternal(HttpServletRequest, HttpServletResponse, FilterChain)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="42" alt="42"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">13</td><td class="ctr2" id="i0">13</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="MetricsFilter.java.html#L26" class="el_method">MetricsFilter(MeterRegistry)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/MetricsFilter.java.html b/testResult/html/stirling.software.SPDF.config/MetricsFilter.java.html
    new file mode 100644
    index 000000000..4964c7dec
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/MetricsFilter.java.html
    @@ -0,0 +1,52 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsFilter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">MetricsFilter.java</span></div><h1>MetricsFilter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.IOException;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.filter.OncePerRequestFilter;
    +
    +import io.micrometer.core.instrument.Counter;
    +import io.micrometer.core.instrument.MeterRegistry;
    +
    +import jakarta.servlet.FilterChain;
    +import jakarta.servlet.ServletException;
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +import jakarta.servlet.http.HttpSession;
    +
    +import stirling.software.SPDF.utils.RequestUriUtils;
    +
    +@Component
    +public class MetricsFilter extends OncePerRequestFilter {
    +
    +    private final MeterRegistry meterRegistry;
    +
    +    @Autowired
    +<span class="nc" id="L26">    public MetricsFilter(MeterRegistry meterRegistry) {</span>
    +<span class="nc" id="L27">        this.meterRegistry = meterRegistry;</span>
    +<span class="nc" id="L28">    }</span>
    +
    +    @Override
    +    protected void doFilterInternal(
    +            HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
    +            throws ServletException, IOException {
    +<span class="nc" id="L34">        String uri = request.getRequestURI();</span>
    +
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">        if (RequestUriUtils.isTrackableResource(request.getContextPath(), uri)) {</span>
    +<span class="nc" id="L37">            HttpSession session = request.getSession(false);</span>
    +<span class="nc bnc" id="L38" title="All 2 branches missed.">            String sessionId = (session != null) ? session.getId() : &quot;no-session&quot;;</span>
    +<span class="nc" id="L39">            Counter counter =</span>
    +<span class="nc" id="L40">                    Counter.builder(&quot;http.requests&quot;)</span>
    +<span class="nc" id="L41">                            .tag(&quot;session&quot;, sessionId)</span>
    +<span class="nc" id="L42">                            .tag(&quot;method&quot;, request.getMethod())</span>
    +<span class="nc" id="L43">                            .tag(&quot;uri&quot;, uri)</span>
    +<span class="nc" id="L44">                            .register(meterRegistry);</span>
    +
    +<span class="nc" id="L46">            counter.increment();</span>
    +        }
    +
    +<span class="nc" id="L49">        filterChain.doFilter(request, response);</span>
    +<span class="nc" id="L50">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/OpenApiConfig.html b/testResult/html/stirling.software.SPDF.config/OpenApiConfig.html
    new file mode 100644
    index 000000000..08f00d1d3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/OpenApiConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OpenApiConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">OpenApiConfig</span></div><h1>OpenApiConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">76 of 76</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">24</td><td class="ctr2">24</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="OpenApiConfig.java.html#L25" class="el_method">customOpenAPI()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="70" alt="70"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">21</td><td class="ctr2" id="i0">21</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="OpenApiConfig.java.html#L19" class="el_method">OpenApiConfig(ApplicationProperties)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/OpenApiConfig.java.html b/testResult/html/stirling.software.SPDF.config/OpenApiConfig.java.html
    new file mode 100644
    index 000000000..c223bc9ea
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/OpenApiConfig.java.html
    @@ -0,0 +1,57 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OpenApiConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">OpenApiConfig.java</span></div><h1>OpenApiConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +
    +import io.swagger.v3.oas.models.Components;
    +import io.swagger.v3.oas.models.OpenAPI;
    +import io.swagger.v3.oas.models.info.Info;
    +import io.swagger.v3.oas.models.security.SecurityRequirement;
    +import io.swagger.v3.oas.models.security.SecurityScheme;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +@Configuration
    +public class OpenApiConfig {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L19">    public OpenApiConfig(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L20">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L21">    }</span>
    +
    +    @Bean
    +    public OpenAPI customOpenAPI() {
    +<span class="nc" id="L25">        String version = getClass().getPackage().getImplementationVersion();</span>
    +<span class="nc bnc" id="L26" title="All 2 branches missed.">        if (version == null) {</span>
    +            // default version if all else fails
    +<span class="nc" id="L28">            version = &quot;1.0.0&quot;;</span>
    +        }
    +<span class="nc" id="L30">        SecurityScheme apiKeyScheme =</span>
    +                new SecurityScheme()
    +<span class="nc" id="L32">                        .type(SecurityScheme.Type.APIKEY)</span>
    +<span class="nc" id="L33">                        .in(SecurityScheme.In.HEADER)</span>
    +<span class="nc" id="L34">                        .name(&quot;X-API-KEY&quot;);</span>
    +<span class="nc bnc" id="L35" title="All 2 branches missed.">        if (!applicationProperties.getSecurity().getEnableLogin()) {</span>
    +<span class="nc" id="L36">            return new OpenAPI()</span>
    +<span class="nc" id="L37">                    .components(new Components())</span>
    +<span class="nc" id="L38">                    .info(</span>
    +                            new Info()
    +<span class="nc" id="L40">                                    .title(&quot;Stirling PDF API&quot;)</span>
    +<span class="nc" id="L41">                                    .version(version)</span>
    +<span class="nc" id="L42">                                    .description(</span>
    +                                            &quot;API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.&quot;));
    +        } else {
    +<span class="nc" id="L45">            return new OpenAPI()</span>
    +<span class="nc" id="L46">                    .components(new Components().addSecuritySchemes(&quot;apiKey&quot;, apiKeyScheme))</span>
    +<span class="nc" id="L47">                    .info(</span>
    +                            new Info()
    +<span class="nc" id="L49">                                    .title(&quot;Stirling PDF API&quot;)</span>
    +<span class="nc" id="L50">                                    .version(version)</span>
    +<span class="nc" id="L51">                                    .description(</span>
    +                                            &quot;API documentation for all Server-Side processing.\nPlease note some functionality might be UI only and missing from here.&quot;))
    +<span class="nc" id="L53">                    .addSecurityItem(new SecurityRequirement().addList(&quot;apiKey&quot;));</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/PostHogConfig.html b/testResult/html/stirling.software.SPDF.config/PostHogConfig.html
    new file mode 100644
    index 000000000..3abc5c4bd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/PostHogConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PostHogConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">PostHogConfig</span></div><h1>PostHogConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">32 of 32</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a0"><a href="PostHogConfig.java.html#L27" class="el_method">postHogClient()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="PostHogConfig.java.html#L37" class="el_method">shutdownPostHog()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="7" alt="7"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="PostHogConfig.java.html#L14" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="PostHogConfig.java.html#L15" class="el_method">PostHogConfig()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/PostHogConfig.java.html b/testResult/html/stirling.software.SPDF.config/PostHogConfig.java.html
    new file mode 100644
    index 000000000..78cb09101
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/PostHogConfig.java.html
    @@ -0,0 +1,42 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PostHogConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">PostHogConfig.java</span></div><h1>PostHogConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import org.springframework.beans.factory.annotation.Value;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +
    +import com.posthog.java.PostHog;
    +
    +import jakarta.annotation.PreDestroy;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +@Configuration
    +<span class="nc" id="L14">@Slf4j</span>
    +<span class="nc" id="L15">public class PostHogConfig {</span>
    +
    +    @Value(&quot;${posthog.api.key}&quot;)
    +    private String posthogApiKey;
    +
    +    @Value(&quot;${posthog.host}&quot;)
    +    private String posthogHost;
    +
    +    private PostHog postHogClient;
    +
    +    @Bean
    +    public PostHog postHogClient() {
    +<span class="nc" id="L27">        postHogClient =</span>
    +                new PostHog.Builder(posthogApiKey)
    +<span class="nc" id="L29">                        .host(posthogHost)</span>
    +<span class="nc" id="L30">                        .logger(new PostHogLoggerImpl())</span>
    +<span class="nc" id="L31">                        .build();</span>
    +<span class="nc" id="L32">        return postHogClient;</span>
    +    }
    +
    +    @PreDestroy
    +    public void shutdownPostHog() {
    +<span class="nc bnc" id="L37" title="All 2 branches missed.">        if (postHogClient != null) {</span>
    +<span class="nc" id="L38">            postHogClient.shutdown();</span>
    +        }
    +<span class="nc" id="L40">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/PostHogLoggerImpl.html b/testResult/html/stirling.software.SPDF.config/PostHogLoggerImpl.html
    new file mode 100644
    index 000000000..cde521a0e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/PostHogLoggerImpl.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PostHogLoggerImpl</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">PostHogLoggerImpl</span></div><h1>PostHogLoggerImpl</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">36 of 36</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a2"><a href="PostHogLoggerImpl.java.html#L35" class="el_method">error(String, Throwable)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PostHogLoggerImpl.java.html#L15" class="el_method">debug(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="PostHogLoggerImpl.java.html#L20" class="el_method">info(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="PostHogLoggerImpl.java.html#L25" class="el_method">warn(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="PostHogLoggerImpl.java.html#L30" class="el_method">error(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="PostHogLoggerImpl.java.html#L9" class="el_method">static {...}</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="4" alt="4"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="PostHogLoggerImpl.java.html#L11" class="el_method">PostHogLoggerImpl()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/PostHogLoggerImpl.java.html b/testResult/html/stirling.software.SPDF.config/PostHogLoggerImpl.java.html
    new file mode 100644
    index 000000000..a46af47d7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/PostHogLoggerImpl.java.html
    @@ -0,0 +1,43 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PostHogLoggerImpl.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">PostHogLoggerImpl.java</span></div><h1>PostHogLoggerImpl.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import org.springframework.stereotype.Component;
    +
    +import com.posthog.java.PostHogLogger;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +<span class="nc" id="L9">@Slf4j</span>
    +@Component
    +<span class="nc" id="L11">public class PostHogLoggerImpl implements PostHogLogger {</span>
    +
    +    @Override
    +    public void debug(String message) {
    +<span class="nc" id="L15">        log.debug(message);</span>
    +<span class="nc" id="L16">    }</span>
    +
    +    @Override
    +    public void info(String message) {
    +<span class="nc" id="L20">        log.info(message);</span>
    +<span class="nc" id="L21">    }</span>
    +
    +    @Override
    +    public void warn(String message) {
    +<span class="nc" id="L25">        log.warn(message);</span>
    +<span class="nc" id="L26">    }</span>
    +
    +    @Override
    +    public void error(String message) {
    +<span class="nc" id="L30">        log.error(message);</span>
    +<span class="nc" id="L31">    }</span>
    +
    +    @Override
    +    public void error(String message, Throwable throwable) {
    +<span class="nc bnc" id="L35" title="All 2 branches missed.">        if (message.contains(&quot;Error sending events to PostHog&quot;)) {</span>
    +<span class="nc" id="L36">            log.warn(</span>
    +                    &quot;Error sending metrics, Likely caused by no internet connection. Non Blocking&quot;);
    +        } else {
    +<span class="nc" id="L39">            log.error(message, throwable);</span>
    +        }
    +<span class="nc" id="L41">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/RuntimePathConfig.html b/testResult/html/stirling.software.SPDF.config/RuntimePathConfig.html
    new file mode 100644
    index 000000000..8f1733080
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/RuntimePathConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RuntimePathConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">RuntimePathConfig</span></div><h1>RuntimePathConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">149 of 153</td><td class="ctr2">2%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">11</td><td class="ctr2">12</td><td class="ctr1">30</td><td class="ctr2">31</td><td class="ctr1">3</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="RuntimePathConfig.java.html#L31" class="el_method">RuntimePathConfig(ApplicationProperties)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="134" alt="134"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h0">28</td><td class="ctr2" id="i0">28</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="RuntimePathConfig.java.html#L77" class="el_method">isRunningInDocker()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="8" alt="8"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="RuntimePathConfig.java.html#L73" class="el_method">resolvePath(String, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="7" alt="7"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="RuntimePathConfig.java.html#L16" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/greenbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">0</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">0</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">0</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/RuntimePathConfig.java.html b/testResult/html/stirling.software.SPDF.config/RuntimePathConfig.java.html
    new file mode 100644
    index 000000000..affded13a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/RuntimePathConfig.java.html
    @@ -0,0 +1,80 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RuntimePathConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">RuntimePathConfig.java</span></div><h1>RuntimePathConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +
    +import org.apache.commons.lang3.StringUtils;
    +import org.springframework.context.annotation.Configuration;
    +
    +import lombok.Getter;
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Operations;
    +import stirling.software.SPDF.model.ApplicationProperties.CustomPaths.Pipeline;
    +
    +<span class="fc" id="L16">@Slf4j</span>
    +@Configuration
    +@Getter
    +public class RuntimePathConfig {
    +    private final ApplicationProperties properties;
    +    private final String basePath;
    +    private final String weasyPrintPath;
    +    private final String unoConvertPath;
    +
    +    // Pipeline paths
    +    private final String pipelineWatchedFoldersPath;
    +    private final String pipelineFinishedFoldersPath;
    +    private final String pipelineDefaultWebUiConfigs;
    +    private final String pipelinePath;
    +
    +<span class="nc" id="L31">    public RuntimePathConfig(ApplicationProperties properties) {</span>
    +<span class="nc" id="L32">        this.properties = properties;</span>
    +<span class="nc" id="L33">        this.basePath = InstallationPathConfig.getPath();</span>
    +
    +<span class="nc" id="L35">        this.pipelinePath = Path.of(basePath, &quot;pipeline&quot;).toString();</span>
    +<span class="nc" id="L36">        String defaultWatchedFolders = Path.of(this.pipelinePath, &quot;watchedFolders&quot;).toString();</span>
    +<span class="nc" id="L37">        String defaultFinishedFolders = Path.of(this.pipelinePath, &quot;finishedFolders&quot;).toString();</span>
    +<span class="nc" id="L38">        String defaultWebUIConfigs = Path.of(this.pipelinePath, &quot;defaultWebUIConfigs&quot;).toString();</span>
    +
    +<span class="nc" id="L40">        Pipeline pipeline = properties.getSystem().getCustomPaths().getPipeline();</span>
    +
    +<span class="nc" id="L42">        this.pipelineWatchedFoldersPath =</span>
    +<span class="nc" id="L43">                resolvePath(</span>
    +                        defaultWatchedFolders,
    +<span class="nc bnc" id="L45" title="All 2 branches missed.">                        pipeline != null ? pipeline.getWatchedFoldersDir() : null);</span>
    +<span class="nc" id="L46">        this.pipelineFinishedFoldersPath =</span>
    +<span class="nc" id="L47">                resolvePath(</span>
    +                        defaultFinishedFolders,
    +<span class="nc bnc" id="L49" title="All 2 branches missed.">                        pipeline != null ? pipeline.getFinishedFoldersDir() : null);</span>
    +<span class="nc" id="L50">        this.pipelineDefaultWebUiConfigs =</span>
    +<span class="nc" id="L51">                resolvePath(</span>
    +                        defaultWebUIConfigs,
    +<span class="nc bnc" id="L53" title="All 2 branches missed.">                        pipeline != null ? pipeline.getWebUIConfigsDir() : null);</span>
    +
    +<span class="nc" id="L55">        boolean isDocker = isRunningInDocker();</span>
    +
    +        // Initialize Operation paths
    +<span class="nc bnc" id="L58" title="All 2 branches missed.">        String defaultWeasyPrintPath = isDocker ? &quot;/opt/venv/bin/weasyprint&quot; : &quot;weasyprint&quot;;</span>
    +<span class="nc bnc" id="L59" title="All 2 branches missed.">        String defaultUnoConvertPath = isDocker ? &quot;/opt/venv/bin/unoconvert&quot; : &quot;unoconvert&quot;;</span>
    +
    +<span class="nc" id="L61">        Operations operations = properties.getSystem().getCustomPaths().getOperations();</span>
    +<span class="nc" id="L62">        this.weasyPrintPath =</span>
    +<span class="nc" id="L63">                resolvePath(</span>
    +                        defaultWeasyPrintPath,
    +<span class="nc bnc" id="L65" title="All 2 branches missed.">                        operations != null ? operations.getWeasyprint() : null);</span>
    +<span class="nc" id="L66">        this.unoConvertPath =</span>
    +<span class="nc" id="L67">                resolvePath(</span>
    +                        defaultUnoConvertPath,
    +<span class="nc bnc" id="L69" title="All 2 branches missed.">                        operations != null ? operations.getUnoconvert() : null);</span>
    +<span class="nc" id="L70">    }</span>
    +
    +    private String resolvePath(String defaultPath, String customPath) {
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">        return StringUtils.isNotBlank(customPath) ? customPath : defaultPath;</span>
    +    }
    +
    +    private boolean isRunningInDocker() {
    +<span class="nc" id="L77">        return Files.exists(Path.of(&quot;/.dockerenv&quot;));</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/StartupApplicationListener.html b/testResult/html/stirling.software.SPDF.config/StartupApplicationListener.html
    new file mode 100644
    index 000000000..2bdc92ccd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/StartupApplicationListener.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StartupApplicationListener</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">StartupApplicationListener</span></div><h1>StartupApplicationListener</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="StartupApplicationListener.java.html#L10" class="el_method">StartupApplicationListener()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="3" alt="3"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="StartupApplicationListener.java.html#L16" class="el_method">onApplicationEvent(ContextRefreshedEvent)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/StartupApplicationListener.java.html b/testResult/html/stirling.software.SPDF.config/StartupApplicationListener.java.html
    new file mode 100644
    index 000000000..728d4fde8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/StartupApplicationListener.java.html
    @@ -0,0 +1,19 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StartupApplicationListener.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">StartupApplicationListener.java</span></div><h1>StartupApplicationListener.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.time.LocalDateTime;
    +
    +import org.springframework.context.ApplicationListener;
    +import org.springframework.context.event.ContextRefreshedEvent;
    +import org.springframework.stereotype.Component;
    +
    +@Component
    +<span class="nc" id="L10">public class StartupApplicationListener implements ApplicationListener&lt;ContextRefreshedEvent&gt; {</span>
    +
    +    public static LocalDateTime startTime;
    +
    +    @Override
    +    public void onApplicationEvent(ContextRefreshedEvent event) {
    +<span class="nc" id="L16">        startTime = LocalDateTime.now();</span>
    +<span class="nc" id="L17">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/WebMvcConfig.html b/testResult/html/stirling.software.SPDF.config/WebMvcConfig.html
    new file mode 100644
    index 000000000..49ff0b79e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/WebMvcConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>WebMvcConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">WebMvcConfig</span></div><h1>WebMvcConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="WebMvcConfig.java.html#L25" class="el_method">addResourceHandlers(ResourceHandlerRegistry)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="WebMvcConfig.java.html#L13" class="el_method">WebMvcConfig(EndpointInterceptor)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="WebMvcConfig.java.html#L19" class="el_method">addInterceptors(InterceptorRegistry)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/WebMvcConfig.java.html b/testResult/html/stirling.software.SPDF.config/WebMvcConfig.java.html
    new file mode 100644
    index 000000000..9d6853b03
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/WebMvcConfig.java.html
    @@ -0,0 +1,31 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>WebMvcConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">WebMvcConfig.java</span></div><h1>WebMvcConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    +
    +@Configuration
    +public class WebMvcConfig implements WebMvcConfigurer {
    +
    +    private final EndpointInterceptor endpointInterceptor;
    +
    +<span class="nc" id="L13">    public WebMvcConfig(EndpointInterceptor endpointInterceptor) {</span>
    +<span class="nc" id="L14">        this.endpointInterceptor = endpointInterceptor;</span>
    +<span class="nc" id="L15">    }</span>
    +
    +    @Override
    +    public void addInterceptors(InterceptorRegistry registry) {
    +<span class="nc" id="L19">        registry.addInterceptor(endpointInterceptor);</span>
    +<span class="nc" id="L20">    }</span>
    +
    +    @Override
    +    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    +        // Handler for external static resources
    +<span class="nc" id="L25">        registry.addResourceHandler(&quot;/**&quot;)</span>
    +<span class="nc" id="L26">                .addResourceLocations(</span>
    +<span class="nc" id="L27">                        &quot;file:&quot; + InstallationPathConfig.getStaticPath(), &quot;classpath:/static/&quot;);</span>
    +        // .setCachePeriod(0); // Optional: disable caching
    +<span class="nc" id="L29">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/YamlHelper$1.html b/testResult/html/stirling.software.SPDF.config/YamlHelper$1.html
    new file mode 100644
    index 000000000..38b85f31d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/YamlHelper$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>YamlHelper.new StreamDataWriter() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">YamlHelper.new StreamDataWriter() {...}</span></div><h1>YamlHelper.new StreamDataWriter() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">21 of 21</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="YamlHelper.java.html#L358" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="YamlHelper.java.html#L366" class="el_method">write(String, int, int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="93" height="10" title="7" alt="7"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="YamlHelper.java.html#L361" class="el_method">write(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="5" alt="5"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/YamlHelper.html b/testResult/html/stirling.software.SPDF.config/YamlHelper.html
    new file mode 100644
    index 000000000..16421b171
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/YamlHelper.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>YamlHelper</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">YamlHelper</span></div><h1>YamlHelper</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">835 of 835</td><td class="ctr2">0%</td><td class="bar">152 of 152</td><td class="ctr2">0%</td><td class="ctr1">103</td><td class="ctr2">103</td><td class="ctr1">183</td><td class="ctr2">183</td><td class="ctr1">27</td><td class="ctr2">27</td></tr></tfoot><tbody><tr><td id="a23"><a href="YamlHelper.java.html#L109" class="el_method">updateValue(Node, List, Object)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="270" alt="270"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="48" alt="48"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">25</td><td class="ctr2" id="g0">25</td><td class="ctr1" id="h0">58</td><td class="ctr2" id="i0">58</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a9"><a href="YamlHelper.java.html#L198" class="el_method">getValueByExactKeyPath(Node, Deque)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="114" alt="114"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="55" height="10" title="22" alt="22"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">12</td><td class="ctr2" id="g1">12</td><td class="ctr1" id="h1">23</td><td class="ctr2" id="i1">23</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a24"><a href="YamlHelper.java.html#L78" class="el_method">updateValuesFromYaml(YamlHelper, YamlHelper)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="53" alt="53"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="10" alt="10"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">6</td><td class="ctr2" id="g2">6</td><td class="ctr1" id="h2">13</td><td class="ctr2" id="i2">13</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="YamlHelper.java.html#L267" class="el_method">collectKeys(Node, String, Set)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="49" alt="49"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f5">5</td><td class="ctr2" id="g5">5</td><td class="ctr1" id="h5">10</td><td class="ctr2" id="i5">10</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="YamlHelper.java.html#L468" class="el_method">copyComments(Node, Node)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="27" alt="27"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="10" alt="10"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">6</td><td class="ctr2" id="g3">6</td><td class="ctr1" id="h6">8</td><td class="ctr2" id="i6">8</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="YamlHelper.java.html#L288" class="el_method">getRootNode()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="26" alt="26"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f11">3</td><td class="ctr2" id="g11">3</td><td class="ctr1" id="h7">7</td><td class="ctr2" id="i7">7</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a14"><a href="YamlHelper.java.html#L391" class="el_method">isInteger(Object)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="26" alt="26"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="10" alt="10"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f4">6</td><td class="ctr2" id="g4">6</td><td class="ctr1" id="h9">5</td><td class="ctr2" id="i9">5</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a26"><a href="YamlHelper.java.html#L48" class="el_method">YamlHelper(Path)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="22" alt="22"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h3">11</td><td class="ctr2" id="i3">11</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a25"><a href="YamlHelper.java.html#L48" class="el_method">YamlHelper(LoadSettings, String)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="21" alt="21"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h4">11</td><td class="ctr2" id="i4">11</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a1"><a href="YamlHelper.java.html#L356" class="el_method">convertNodeToYaml(Node)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="20" alt="20"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h10">4</td><td class="ctr2" id="i10">4</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a13"><a href="YamlHelper.java.html#L411" class="el_method">isFloat(Object)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="20" alt="20"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="8" alt="8"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f6">5</td><td class="ctr2" id="g6">5</td><td class="ctr1" id="h16">2</td><td class="ctr2" id="i16">2</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a18"><a href="YamlHelper.java.html#L339" class="el_method">save(Path)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="18" alt="18"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f12">2</td><td class="ctr2" id="g12">2</td><td class="ctr1" id="h12">3</td><td class="ctr2" id="i12">3</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a17"><a href="YamlHelper.java.html#L423" class="el_method">isShort(Object)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="17" alt="17"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="6" alt="6"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f8">4</td><td class="ctr2" id="g8">4</td><td class="ctr1" id="h17">2</td><td class="ctr2" id="i17">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a12"><a href="YamlHelper.java.html#L435" class="el_method">isByte(Object)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="17" alt="17"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="6" alt="6"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f9">4</td><td class="ctr2" id="g9">4</td><td class="ctr1" id="h18">2</td><td class="ctr2" id="i18">2</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a15"><a href="YamlHelper.java.html#L447" class="el_method">isLong(Object)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="17" alt="17"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="6" alt="6"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f10">4</td><td class="ctr2" id="g10">4</td><td class="ctr1" id="h19">2</td><td class="ctr2" id="i19">2</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a11"><a href="YamlHelper.java.html#L458" class="el_method">isAnyInteger(Object)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="16" alt="16"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="8" alt="8"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f7">5</td><td class="ctr2" id="g7">5</td><td class="ctr1" id="h22">1</td><td class="ctr2" id="i22">1</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a21"><a href="YamlHelper.java.html#L35" class="el_method">static {...}</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="13" alt="13"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h8">7</td><td class="ctr2" id="i8">7</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a3"><a href="YamlHelper.java.html#L240" class="el_method">getAllKeys()</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="12" alt="12"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f13">2</td><td class="ctr2" id="g13">2</td><td class="ctr1" id="h13">3</td><td class="ctr2" id="i13">3</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a4"><a href="YamlHelper.java.html#L254" class="el_method">getAllKeys(Node)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="11" alt="11"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h14">3</td><td class="ctr2" id="i14">3</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a10"><a href="YamlHelper.java.html#L194" class="el_method">getValueByExactKeyPath(String[])</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="10" alt="10"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h23">1</td><td class="ctr2" id="i23">1</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a8"><a href="YamlHelper.java.html#L314" class="el_method">getUpdatedRootNode()</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="10" alt="10"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f14">2</td><td class="ctr2" id="g14">2</td><td class="ctr1" id="h15">3</td><td class="ctr2" id="i15">3</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a19"><a href="YamlHelper.java.html#L346" class="el_method">saveOverride(Path)</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="10" alt="10"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h20">2</td><td class="ctr2" id="i20">2</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a16"><a href="YamlHelper.java.html#L376" class="el_method">isParsable(String, Function)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="9" alt="9"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h11">4</td><td class="ctr2" id="i11">4</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a5"><a href="YamlHelper.java.html#L326" class="el_method">getParserImpl()</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="8" alt="8"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h24">1</td><td class="ctr2" id="i24">1</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a7"><a href="YamlHelper.java.html#L335" class="el_method">getStreamReader()</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="8" alt="8"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h25">1</td><td class="ctr2" id="i25">1</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr><tr><td id="a22"><a href="YamlHelper.java.html#L105" class="el_method">updateValue(List, Object)</a></td><td class="bar" id="b25"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="7" alt="7"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g25">1</td><td class="ctr1" id="h26">1</td><td class="ctr2" id="i26">1</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k25">1</td></tr><tr><td id="a20"><a href="YamlHelper.java.html#L305" class="el_method">setNewNode(Node)</a></td><td class="bar" id="b26"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c26">0%</td><td class="bar" id="d26"/><td class="ctr2" id="e26">n/a</td><td class="ctr1" id="f26">1</td><td class="ctr2" id="g26">1</td><td class="ctr1" id="h21">2</td><td class="ctr2" id="i21">2</td><td class="ctr1" id="j26">1</td><td class="ctr2" id="k26">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/YamlHelper.java.html b/testResult/html/stirling.software.SPDF.config/YamlHelper.java.html
    new file mode 100644
    index 000000000..577be1bfa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/YamlHelper.java.html
    @@ -0,0 +1,480 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>YamlHelper.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">YamlHelper.java</span></div><h1>YamlHelper.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.IOException;
    +import java.io.StringWriter;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayDeque;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Deque;
    +import java.util.LinkedHashSet;
    +import java.util.List;
    +import java.util.Optional;
    +import java.util.Set;
    +import java.util.function.Function;
    +
    +import org.snakeyaml.engine.v2.api.Dump;
    +import org.snakeyaml.engine.v2.api.DumpSettings;
    +import org.snakeyaml.engine.v2.api.LoadSettings;
    +import org.snakeyaml.engine.v2.api.StreamDataWriter;
    +import org.snakeyaml.engine.v2.common.FlowStyle;
    +import org.snakeyaml.engine.v2.common.ScalarStyle;
    +import org.snakeyaml.engine.v2.composer.Composer;
    +import org.snakeyaml.engine.v2.nodes.MappingNode;
    +import org.snakeyaml.engine.v2.nodes.Node;
    +import org.snakeyaml.engine.v2.nodes.NodeTuple;
    +import org.snakeyaml.engine.v2.nodes.ScalarNode;
    +import org.snakeyaml.engine.v2.nodes.SequenceNode;
    +import org.snakeyaml.engine.v2.nodes.Tag;
    +import org.snakeyaml.engine.v2.parser.ParserImpl;
    +import org.snakeyaml.engine.v2.scanner.StreamReader;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +<span class="nc" id="L35">@Slf4j</span>
    +public class YamlHelper {
    +
    +    // YAML dump settings with comment support and block flow style
    +<span class="nc" id="L39">    private static final DumpSettings DUMP_SETTINGS =</span>
    +<span class="nc" id="L40">            DumpSettings.builder()</span>
    +<span class="nc" id="L41">                    .setDumpComments(true)</span>
    +<span class="nc" id="L42">                    .setWidth(Integer.MAX_VALUE)</span>
    +<span class="nc" id="L43">                    .setDefaultFlowStyle(FlowStyle.BLOCK)</span>
    +<span class="nc" id="L44">                    .build();</span>
    +
    +    private final String yamlContent; // Stores the entire YAML content as a string
    +
    +<span class="nc" id="L48">    private LoadSettings loadSettings =</span>
    +<span class="nc" id="L49">            LoadSettings.builder()</span>
    +<span class="nc" id="L50">                    .setUseMarks(true)</span>
    +<span class="nc" id="L51">                    .setMaxAliasesForCollections(Integer.MAX_VALUE)</span>
    +<span class="nc" id="L52">                    .setAllowRecursiveKeys(true)</span>
    +<span class="nc" id="L53">                    .setParseComments(true)</span>
    +<span class="nc" id="L54">                    .build();</span>
    +
    +    private Path originalFilePath;
    +    private Node updatedRootNode;
    +
    +    // Constructor with custom LoadSettings and YAML string
    +<span class="nc" id="L60">    public YamlHelper(LoadSettings loadSettings, String yamlContent) {</span>
    +<span class="nc" id="L61">        this.loadSettings = loadSettings;</span>
    +<span class="nc" id="L62">        this.yamlContent = yamlContent;</span>
    +<span class="nc" id="L63">    }</span>
    +
    +    // Constructor that reads YAML from a file path
    +<span class="nc" id="L66">    public YamlHelper(Path originalFilePath) throws IOException {</span>
    +<span class="nc" id="L67">        this.yamlContent = Files.readString(originalFilePath);</span>
    +<span class="nc" id="L68">        this.originalFilePath = originalFilePath;</span>
    +<span class="nc" id="L69">    }</span>
    +
    +    /**
    +     * Updates values in the target YAML based on values from the source YAML. It ensures that only
    +     * existing keys in the target YAML are updated.
    +     *
    +     * @return true if at least one key was updated, false otherwise.
    +     */
    +    public boolean updateValuesFromYaml(YamlHelper sourceYaml, YamlHelper targetYaml) {
    +<span class="nc" id="L78">        boolean updated = false;</span>
    +<span class="nc" id="L79">        Set&lt;String&gt; sourceKeys = sourceYaml.getAllKeys();</span>
    +<span class="nc" id="L80">        Set&lt;String&gt; targetKeys = targetYaml.getAllKeys();</span>
    +
    +<span class="nc bnc" id="L82" title="All 2 branches missed.">        for (String key : sourceKeys) {</span>
    +<span class="nc" id="L83">            String[] keyArray = key.split(&quot;\\.&quot;);</span>
    +
    +<span class="nc" id="L85">            Object newValue = sourceYaml.getValueByExactKeyPath(keyArray);</span>
    +<span class="nc" id="L86">            Object currentValue = targetYaml.getValueByExactKeyPath(keyArray);</span>
    +<span class="nc bnc" id="L87" title="All 2 branches missed.">            if (newValue != null</span>
    +<span class="nc bnc" id="L88" title="All 4 branches missed.">                    &amp;&amp; (!newValue.equals(currentValue) || !sourceKeys.equals(targetKeys))) {</span>
    +<span class="nc" id="L89">                boolean updatedKey = targetYaml.updateValue(Arrays.asList(keyArray), newValue);</span>
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">                if (updatedKey) updated = true;</span>
    +            }
    +<span class="nc" id="L92">        }</span>
    +
    +<span class="nc" id="L94">        return updated;</span>
    +    }
    +
    +    /**
    +     * Updates a value in the YAML structure.
    +     *
    +     * @param keys The hierarchical keys leading to the value.
    +     * @param newValue The new value to set.
    +     * @return true if the value was updated, false otherwise.
    +     */
    +    public boolean updateValue(List&lt;String&gt; keys, Object newValue) {
    +<span class="nc" id="L105">        return updateValue(getRootNode(), keys, newValue);</span>
    +    }
    +
    +    private boolean updateValue(Node node, List&lt;String&gt; keys, Object newValue) {
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">        if (!(node instanceof MappingNode mappingNode)) return false;</span>
    +
    +<span class="nc" id="L111">        List&lt;NodeTuple&gt; updatedTuples = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L112">        boolean updated = false;</span>
    +
    +<span class="nc bnc" id="L114" title="All 2 branches missed.">        for (NodeTuple tuple : mappingNode.getValue()) {</span>
    +<span class="nc bnc" id="L115" title="All 2 branches missed.">            ScalarNode keyNode = (tuple.getKeyNode() instanceof ScalarNode sk) ? sk : null;</span>
    +<span class="nc bnc" id="L116" title="All 4 branches missed.">            if (keyNode == null || !keyNode.getValue().equals(keys.get(0))) {</span>
    +<span class="nc" id="L117">                updatedTuples.add(tuple);</span>
    +<span class="nc" id="L118">                continue;</span>
    +            }
    +
    +<span class="nc" id="L121">            Node valueNode = tuple.getValueNode();</span>
    +
    +<span class="nc bnc" id="L123" title="All 2 branches missed.">            if (keys.size() == 1) {</span>
    +<span class="nc" id="L124">                Tag tag = valueNode.getTag();</span>
    +<span class="nc" id="L125">                Node newValueNode = null;</span>
    +
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">                if (isAnyInteger(newValue)) {</span>
    +<span class="nc" id="L128">                    newValueNode =</span>
    +<span class="nc" id="L129">                            new ScalarNode(Tag.INT, String.valueOf(newValue), ScalarStyle.PLAIN);</span>
    +<span class="nc bnc" id="L130" title="All 2 branches missed.">                } else if (isFloat(newValue)) {</span>
    +<span class="nc" id="L131">                    Object floatValue = Float.valueOf(String.valueOf(newValue));</span>
    +<span class="nc" id="L132">                    newValueNode =</span>
    +                            new ScalarNode(
    +<span class="nc" id="L134">                                    Tag.FLOAT, String.valueOf(floatValue), ScalarStyle.PLAIN);</span>
    +<span class="nc bnc" id="L135" title="All 4 branches missed.">                } else if (&quot;true&quot;.equals(newValue) || &quot;false&quot;.equals(newValue)) {</span>
    +<span class="nc" id="L136">                    newValueNode =</span>
    +<span class="nc" id="L137">                            new ScalarNode(Tag.BOOL, String.valueOf(newValue), ScalarStyle.PLAIN);</span>
    +<span class="nc bnc" id="L138" title="All 2 branches missed.">                } else if (newValue instanceof List&lt;?&gt; list) {</span>
    +<span class="nc" id="L139">                    List&lt;Node&gt; sequenceNodes = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">                    for (Object item : list) {</span>
    +<span class="nc" id="L141">                        Object obj = String.valueOf(item);</span>
    +<span class="nc bnc" id="L142" title="All 2 branches missed.">                        if (isAnyInteger(item)) {</span>
    +<span class="nc" id="L143">                            tag = Tag.INT;</span>
    +<span class="nc bnc" id="L144" title="All 2 branches missed.">                        } else if (isFloat(item)) {</span>
    +<span class="nc" id="L145">                            obj = Float.valueOf(String.valueOf(item));</span>
    +<span class="nc" id="L146">                            tag = Tag.FLOAT;</span>
    +<span class="nc bnc" id="L147" title="All 4 branches missed.">                        } else if (&quot;true&quot;.equals(item) || &quot;false&quot;.equals(item)) {</span>
    +<span class="nc" id="L148">                            tag = Tag.BOOL;</span>
    +<span class="nc bnc" id="L149" title="All 4 branches missed.">                        } else if (item == null || &quot;null&quot;.equals(item)) {</span>
    +<span class="nc" id="L150">                            tag = Tag.NULL;</span>
    +                        } else {
    +<span class="nc" id="L152">                            tag = Tag.STR;</span>
    +                        }
    +<span class="nc" id="L154">                        sequenceNodes.add(</span>
    +<span class="nc" id="L155">                                new ScalarNode(tag, String.valueOf(obj), ScalarStyle.PLAIN));</span>
    +<span class="nc" id="L156">                    }</span>
    +<span class="nc" id="L157">                    newValueNode = new SequenceNode(Tag.SEQ, sequenceNodes, FlowStyle.FLOW);</span>
    +<span class="nc bnc" id="L158" title="All 2 branches missed.">                } else if (tag == Tag.NULL) {</span>
    +<span class="nc bnc" id="L159" title="All 2 branches missed.">                    if (&quot;true&quot;.equals(newValue)</span>
    +<span class="nc bnc" id="L160" title="All 4 branches missed.">                            || &quot;false&quot;.equals(newValue)</span>
    +                            || newValue instanceof Boolean) {
    +<span class="nc" id="L162">                        tag = Tag.BOOL;</span>
    +                    }
    +<span class="nc" id="L164">                    newValueNode = new ScalarNode(tag, String.valueOf(newValue), ScalarStyle.PLAIN);</span>
    +                } else {
    +<span class="nc" id="L166">                    newValueNode = new ScalarNode(tag, String.valueOf(newValue), ScalarStyle.PLAIN);</span>
    +                }
    +<span class="nc" id="L168">                copyComments(valueNode, newValueNode);</span>
    +
    +<span class="nc" id="L170">                updatedTuples.add(new NodeTuple(keyNode, newValueNode));</span>
    +<span class="nc" id="L171">                updated = true;</span>
    +<span class="nc bnc" id="L172" title="All 2 branches missed.">            } else if (valueNode instanceof MappingNode) {</span>
    +<span class="nc" id="L173">                updated = updateValue(valueNode, keys.subList(1, keys.size()), newValue);</span>
    +<span class="nc" id="L174">                updatedTuples.add(tuple);</span>
    +            }
    +<span class="nc" id="L176">        }</span>
    +
    +<span class="nc bnc" id="L178" title="All 2 branches missed.">        if (updated) {</span>
    +<span class="nc" id="L179">            mappingNode.getValue().clear();</span>
    +<span class="nc" id="L180">            mappingNode.getValue().addAll(updatedTuples);</span>
    +        }
    +<span class="nc" id="L182">        setNewNode(node);</span>
    +
    +<span class="nc" id="L184">        return updated;</span>
    +    }
    +
    +    /**
    +     * Fetches a value based on an exact key path.
    +     *
    +     * @param keys The key hierarchy leading to the value.
    +     * @return The value if found, otherwise null.
    +     */
    +    public Object getValueByExactKeyPath(String... keys) {
    +<span class="nc" id="L194">        return getValueByExactKeyPath(getRootNode(), new ArrayDeque&lt;&gt;(List.of(keys)));</span>
    +    }
    +
    +    private Object getValueByExactKeyPath(Node node, Deque&lt;String&gt; keyQueue) {
    +<span class="nc bnc" id="L198" title="All 2 branches missed.">        if (!(node instanceof MappingNode mappingNode)) return null;</span>
    +
    +<span class="nc" id="L200">        String currentKey = keyQueue.poll();</span>
    +<span class="nc bnc" id="L201" title="All 2 branches missed.">        if (currentKey == null) return null;</span>
    +
    +<span class="nc bnc" id="L203" title="All 2 branches missed.">        for (NodeTuple tuple : mappingNode.getValue()) {</span>
    +<span class="nc bnc" id="L204" title="All 2 branches missed.">            if (tuple.getKeyNode() instanceof ScalarNode keyNode</span>
    +<span class="nc bnc" id="L205" title="All 2 branches missed.">                    &amp;&amp; keyNode.getValue().equals(currentKey)) {</span>
    +<span class="nc bnc" id="L206" title="All 2 branches missed.">                if (keyQueue.isEmpty()) {</span>
    +<span class="nc" id="L207">                    Node valueNode = tuple.getValueNode();</span>
    +
    +<span class="nc bnc" id="L209" title="All 2 branches missed.">                    if (valueNode instanceof ScalarNode scalarValueNode) {</span>
    +<span class="nc" id="L210">                        return scalarValueNode.getValue();</span>
    +<span class="nc bnc" id="L211" title="All 2 branches missed.">                    } else if (valueNode instanceof MappingNode subMapping) {</span>
    +<span class="nc" id="L212">                        return getValueByExactKeyPath(subMapping, keyQueue);</span>
    +<span class="nc bnc" id="L213" title="All 2 branches missed.">                    } else if (valueNode instanceof SequenceNode sequenceNode) {</span>
    +<span class="nc" id="L214">                        List&lt;Object&gt; valuesList = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L215" title="All 2 branches missed.">                        for (Node o : sequenceNode.getValue()) {</span>
    +<span class="nc bnc" id="L216" title="All 2 branches missed.">                            if (o instanceof ScalarNode scalarValue) {</span>
    +<span class="nc" id="L217">                                valuesList.add(scalarValue.getValue());</span>
    +                            }
    +<span class="nc" id="L219">                        }</span>
    +<span class="nc" id="L220">                        return valuesList;</span>
    +                    } else {
    +<span class="nc" id="L222">                        return null;</span>
    +                    }
    +                }
    +<span class="nc" id="L225">                return getValueByExactKeyPath(tuple.getValueNode(), keyQueue);</span>
    +            }
    +<span class="nc" id="L227">        }</span>
    +<span class="nc" id="L228">        return null;</span>
    +    }
    +
    +    private Set&lt;String&gt; cachedKeys;
    +
    +    /**
    +     * Retrieves the set of all keys present in the YAML structure. Keys are returned as
    +     * dot-separated paths for nested keys.
    +     *
    +     * @return A set containing all keys in dot notation.
    +     */
    +    public Set&lt;String&gt; getAllKeys() {
    +<span class="nc bnc" id="L240" title="All 2 branches missed.">        if (cachedKeys == null) {</span>
    +<span class="nc" id="L241">            cachedKeys = getAllKeys(getRootNode());</span>
    +        }
    +<span class="nc" id="L243">        return cachedKeys;</span>
    +    }
    +
    +    /**
    +     * Collects all keys from the YAML node recursively.
    +     *
    +     * @param node The current YAML node.
    +     * @param currentPath The accumulated path of keys.
    +     * @param allKeys The set storing all collected keys.
    +     */
    +    private Set&lt;String&gt; getAllKeys(Node node) {
    +<span class="nc" id="L254">        Set&lt;String&gt; allKeys = new LinkedHashSet&lt;&gt;();</span>
    +<span class="nc" id="L255">        collectKeys(node, &quot;&quot;, allKeys);</span>
    +<span class="nc" id="L256">        return allKeys;</span>
    +    }
    +
    +    /**
    +     * Recursively traverses the YAML structure to collect all keys.
    +     *
    +     * @param node The current node in the YAML structure.
    +     * @param currentPath The accumulated key path.
    +     * @param allKeys The set storing collected keys.
    +     */
    +    private void collectKeys(Node node, String currentPath, Set&lt;String&gt; allKeys) {
    +<span class="nc bnc" id="L267" title="All 2 branches missed.">        if (node instanceof MappingNode mappingNode) {</span>
    +<span class="nc bnc" id="L268" title="All 2 branches missed.">            for (NodeTuple tuple : mappingNode.getValue()) {</span>
    +<span class="nc bnc" id="L269" title="All 2 branches missed.">                if (tuple.getKeyNode() instanceof ScalarNode keyNode) {</span>
    +                    String newPath =
    +<span class="nc bnc" id="L271" title="All 2 branches missed.">                            currentPath.isEmpty()</span>
    +<span class="nc" id="L272">                                    ? keyNode.getValue()</span>
    +<span class="nc" id="L273">                                    : currentPath + &quot;.&quot; + keyNode.getValue();</span>
    +<span class="nc" id="L274">                    allKeys.add(newPath);</span>
    +<span class="nc" id="L275">                    collectKeys(tuple.getValueNode(), newPath, allKeys);</span>
    +                }
    +<span class="nc" id="L277">            }</span>
    +        }
    +<span class="nc" id="L279">    }</span>
    +
    +    /**
    +     * Retrieves the root node of the YAML document. If a new node was previously set, it is
    +     * returned instead.
    +     *
    +     * @return The root node of the YAML structure.
    +     */
    +    private Node getRootNode() {
    +<span class="nc bnc" id="L288" title="All 2 branches missed.">        if (this.updatedRootNode != null) {</span>
    +<span class="nc" id="L289">            return this.updatedRootNode;</span>
    +        }
    +<span class="nc" id="L291">        Composer composer = new Composer(loadSettings, getParserImpl());</span>
    +<span class="nc" id="L292">        Optional&lt;Node&gt; rootNodeOpt = composer.getSingleNode();</span>
    +<span class="nc bnc" id="L293" title="All 2 branches missed.">        if (rootNodeOpt.isPresent()) {</span>
    +<span class="nc" id="L294">            return rootNodeOpt.get();</span>
    +        }
    +<span class="nc" id="L296">        return null;</span>
    +    }
    +
    +    /**
    +     * Sets a new root node, allowing modifications to be tracked.
    +     *
    +     * @param newRootNode The modified root node.
    +     */
    +    public void setNewNode(Node newRootNode) {
    +<span class="nc" id="L305">        this.updatedRootNode = newRootNode;</span>
    +<span class="nc" id="L306">    }</span>
    +
    +    /**
    +     * Retrieves the current root node (either the original or the updated one).
    +     *
    +     * @return The root node.
    +     */
    +    public Node getUpdatedRootNode() {
    +<span class="nc bnc" id="L314" title="All 2 branches missed.">        if (this.updatedRootNode == null) {</span>
    +<span class="nc" id="L315">            this.updatedRootNode = getRootNode();</span>
    +        }
    +<span class="nc" id="L317">        return this.updatedRootNode;</span>
    +    }
    +
    +    /**
    +     * Initializes the YAML parser.
    +     *
    +     * @return The configured parser.
    +     */
    +    private ParserImpl getParserImpl() {
    +<span class="nc" id="L326">        return new ParserImpl(loadSettings, getStreamReader());</span>
    +    }
    +
    +    /**
    +     * Creates a stream reader for the YAML content.
    +     *
    +     * @return The configured stream reader.
    +     */
    +    private StreamReader getStreamReader() {
    +<span class="nc" id="L335">        return new StreamReader(loadSettings, yamlContent);</span>
    +    }
    +
    +    public MappingNode save(Path saveFilePath) throws IOException {
    +<span class="nc bnc" id="L339" title="All 2 branches missed.">        if (!saveFilePath.equals(originalFilePath)) {</span>
    +<span class="nc" id="L340">            Files.writeString(saveFilePath, convertNodeToYaml(getUpdatedRootNode()));</span>
    +        }
    +<span class="nc" id="L342">        return (MappingNode) getUpdatedRootNode();</span>
    +    }
    +
    +    public void saveOverride(Path saveFilePath) throws IOException {
    +<span class="nc" id="L346">        Files.writeString(saveFilePath, convertNodeToYaml(getUpdatedRootNode()));</span>
    +<span class="nc" id="L347">    }</span>
    +
    +    /**
    +     * Converts a YAML node back to a YAML-formatted string.
    +     *
    +     * @param rootNode The root node to be converted.
    +     * @return A YAML-formatted string.
    +     */
    +    public String convertNodeToYaml(Node rootNode) {
    +<span class="nc" id="L356">        StringWriter writer = new StringWriter();</span>
    +<span class="nc" id="L357">        StreamDataWriter streamDataWriter =</span>
    +<span class="nc" id="L358">                new StreamDataWriter() {</span>
    +                    @Override
    +                    public void write(String str) {
    +<span class="nc" id="L361">                        writer.write(str);</span>
    +<span class="nc" id="L362">                    }</span>
    +
    +                    @Override
    +                    public void write(String str, int off, int len) {
    +<span class="nc" id="L366">                        writer.write(str, off, len);</span>
    +<span class="nc" id="L367">                    }</span>
    +                };
    +
    +<span class="nc" id="L370">        new Dump(DUMP_SETTINGS).dumpNode(rootNode, streamDataWriter);</span>
    +<span class="nc" id="L371">        return writer.toString();</span>
    +    }
    +
    +    private static boolean isParsable(String value, Function&lt;String, ?&gt; parser) {
    +        try {
    +<span class="nc" id="L376">            parser.apply(value);</span>
    +<span class="nc" id="L377">            return true;</span>
    +<span class="nc" id="L378">        } catch (NumberFormatException e) {</span>
    +<span class="nc" id="L379">            return false;</span>
    +        }
    +    }
    +
    +    /**
    +     * Checks if a given object is an integer.
    +     *
    +     * @param object The object to check.
    +     * @return True if the object represents an integer, false otherwise.
    +     */
    +    @SuppressWarnings(&quot;UnnecessaryTemporaryOnConversionFromString&quot;)
    +    public static boolean isInteger(Object object) {
    +<span class="nc bnc" id="L391" title="All 8 branches missed.">        if (object instanceof Integer</span>
    +                || object instanceof Short
    +                || object instanceof Byte
    +                || object instanceof Long) {
    +<span class="nc" id="L395">            return true;</span>
    +        }
    +<span class="nc bnc" id="L397" title="All 2 branches missed.">        if (object instanceof String str) {</span>
    +<span class="nc" id="L398">            return isParsable(str, Integer::parseInt);</span>
    +        }
    +<span class="nc" id="L400">        return false;</span>
    +    }
    +
    +    /**
    +     * Checks if a given object is a floating-point number.
    +     *
    +     * @param object The object to check.
    +     * @return True if the object represents a float, false otherwise.
    +     */
    +    @SuppressWarnings(&quot;UnnecessaryTemporaryOnConversionFromString&quot;)
    +    public static boolean isFloat(Object object) {
    +<span class="nc" id="L411">        return (object instanceof Float || object instanceof Double)</span>
    +<span class="nc bnc" id="L412" title="All 8 branches missed.">                || (object instanceof String str &amp;&amp; isParsable(str, Float::parseFloat));</span>
    +    }
    +
    +    /**
    +     * Checks if a given object is a short integer.
    +     *
    +     * @param object The object to check.
    +     * @return True if the object represents a short integer, false otherwise.
    +     */
    +    @SuppressWarnings(&quot;UnnecessaryTemporaryOnConversionFromString&quot;)
    +    public static boolean isShort(Object object) {
    +<span class="nc" id="L423">        return (object instanceof Long)</span>
    +<span class="nc bnc" id="L424" title="All 6 branches missed.">                || (object instanceof String str &amp;&amp; isParsable(str, Short::parseShort));</span>
    +    }
    +
    +    /**
    +     * Checks if a given object is a byte.
    +     *
    +     * @param object The object to check.
    +     * @return True if the object represents a byte, false otherwise.
    +     */
    +    @SuppressWarnings(&quot;UnnecessaryTemporaryOnConversionFromString&quot;)
    +    public static boolean isByte(Object object) {
    +<span class="nc" id="L435">        return (object instanceof Long)</span>
    +<span class="nc bnc" id="L436" title="All 6 branches missed.">                || (object instanceof String str &amp;&amp; isParsable(str, Byte::parseByte));</span>
    +    }
    +
    +    /**
    +     * Checks if a given object is a long integer.
    +     *
    +     * @param object The object to check.
    +     * @return True if the object represents a long integer, false otherwise.
    +     */
    +    @SuppressWarnings(&quot;UnnecessaryTemporaryOnConversionFromString&quot;)
    +    public static boolean isLong(Object object) {
    +<span class="nc" id="L447">        return (object instanceof Long)</span>
    +<span class="nc bnc" id="L448" title="All 6 branches missed.">                || (object instanceof String str &amp;&amp; isParsable(str, Long::parseLong));</span>
    +    }
    +
    +    /**
    +     * Determines if an object is any type of integer (short, byte, long, or int).
    +     *
    +     * @param object The object to check.
    +     * @return True if the object represents an integer type, false otherwise.
    +     */
    +    public static boolean isAnyInteger(Object object) {
    +<span class="nc bnc" id="L458" title="All 8 branches missed.">        return isInteger(object) || isShort(object) || isByte(object) || isLong(object);</span>
    +    }
    +
    +    /**
    +     * Copies comments from an old node to a new one.
    +     *
    +     * @param oldNode The original node with comments.
    +     * @param newValueNode The new node to which comments should be copied.
    +     */
    +    private void copyComments(Node oldNode, Node newValueNode) {
    +<span class="nc bnc" id="L468" title="All 4 branches missed.">        if (oldNode == null || newValueNode == null) return;</span>
    +<span class="nc bnc" id="L469" title="All 2 branches missed.">        if (oldNode.getBlockComments() != null) {</span>
    +<span class="nc" id="L470">            newValueNode.setBlockComments(oldNode.getBlockComments());</span>
    +        }
    +<span class="nc bnc" id="L472" title="All 2 branches missed.">        if (oldNode.getInLineComments() != null) {</span>
    +<span class="nc" id="L473">            newValueNode.setInLineComments(oldNode.getInLineComments());</span>
    +        }
    +<span class="nc bnc" id="L475" title="All 2 branches missed.">        if (oldNode.getEndComments() != null) {</span>
    +<span class="nc" id="L476">            newValueNode.setEndComments(oldNode.getEndComments());</span>
    +        }
    +<span class="nc" id="L478">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/YamlPropertySourceFactory.html b/testResult/html/stirling.software.SPDF.config/YamlPropertySourceFactory.html
    new file mode 100644
    index 000000000..42f3ab0ec
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/YamlPropertySourceFactory.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>YamlPropertySourceFactory</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_class">YamlPropertySourceFactory</span></div><h1>YamlPropertySourceFactory</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">27 of 27</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="YamlPropertySourceFactory.java.html#L17" class="el_method">createPropertySource(String, EncodedResource)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="YamlPropertySourceFactory.java.html#L12" class="el_method">YamlPropertySourceFactory()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/YamlPropertySourceFactory.java.html b/testResult/html/stirling.software.SPDF.config/YamlPropertySourceFactory.java.html
    new file mode 100644
    index 000000000..fb6b525dd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/YamlPropertySourceFactory.java.html
    @@ -0,0 +1,25 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>YamlPropertySourceFactory.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.config</a> &gt; <span class="el_source">YamlPropertySourceFactory.java</span></div><h1>YamlPropertySourceFactory.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.config;
    +
    +import java.io.IOException;
    +import java.util.Properties;
    +
    +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
    +import org.springframework.core.env.PropertiesPropertySource;
    +import org.springframework.core.env.PropertySource;
    +import org.springframework.core.io.support.EncodedResource;
    +import org.springframework.core.io.support.PropertySourceFactory;
    +
    +<span class="nc" id="L12">public class YamlPropertySourceFactory implements PropertySourceFactory {</span>
    +
    +    @Override
    +    public PropertySource&lt;?&gt; createPropertySource(String name, EncodedResource encodedResource)
    +            throws IOException {
    +<span class="nc" id="L17">        YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();</span>
    +<span class="nc" id="L18">        factory.setResources(encodedResource.getResource());</span>
    +<span class="nc" id="L19">        Properties properties = factory.getObject();</span>
    +
    +<span class="nc" id="L21">        return new PropertiesPropertySource(</span>
    +<span class="nc" id="L22">                encodedResource.getResource().getFilename(), properties);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/index.html b/testResult/html/stirling.software.SPDF.config/index.html
    new file mode 100644
    index 000000000..837fcf0b6
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config</span></div><h1>stirling.software.SPDF.config</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4,187 of 4,246</td><td class="ctr2">1%</td><td class="bar">423 of 424</td><td class="ctr2">0%</td><td class="ctr1">376</td><td class="ctr2">382</td><td class="ctr1">989</td><td class="ctr2">1,007</td><td class="ctr1">164</td><td class="ctr2">170</td><td class="ctr1">24</td><td class="ctr2">27</td></tr></tfoot><tbody><tr><td id="a24"><a href="YamlHelper.html" class="el_class">YamlHelper</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="835" alt="835"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="152" alt="152"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">103</td><td class="ctr2" id="g0">103</td><td class="ctr1" id="h1">183</td><td class="ctr2" id="i1">183</td><td class="ctr1" id="j0">27</td><td class="ctr2" id="k0">27</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a4"><a href="EndpointConfiguration.html" class="el_class">EndpointConfiguration</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="115" height="10" title="802" alt="802"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="42" alt="42"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">36</td><td class="ctr2" id="g3">36</td><td class="ctr1" id="h0">200</td><td class="ctr2" id="i0">200</td><td class="ctr1" id="j2">15</td><td class="ctr2" id="k2">15</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a3"><a href="ConfigInitializer.html" class="el_class">ConfigInitializer</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="59" height="10" title="417" alt="417"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="24" alt="24"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f5">16</td><td class="ctr2" id="g5">16</td><td class="ctr1" id="h4">65</td><td class="ctr2" id="i4">65</td><td class="ctr1" id="j9">4</td><td class="ctr2" id="k9">4</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a5"><a href="EndpointInspector.html" class="el_class">EndpointInspector</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="59" height="10" title="411" alt="411"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="62" alt="62"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f1">42</td><td class="ctr2" id="g1">42</td><td class="ctr1" id="h2">111</td><td class="ctr2" id="i2">111</td><td class="ctr1" id="j3">11</td><td class="ctr2" id="k4">11</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a8"><a href="ExternalAppDepConfig.html" class="el_class">ExternalAppDepConfig</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="358" alt="358"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="28" alt="28"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">24</td><td class="ctr2" id="g4">24</td><td class="ctr1" id="h3">78</td><td class="ctr2" id="i3">78</td><td class="ctr1" id="j4">10</td><td class="ctr2" id="k5">10</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a0"><a href="AppConfig.html" class="el_class">AppConfig</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="251" alt="251"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="26" alt="26"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f2">40</td><td class="ctr2" id="g2">40</td><td class="ctr1" id="h5">57</td><td class="ctr2" id="i5">57</td><td class="ctr1" id="j1">27</td><td class="ctr2" id="k1">27</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a2"><a href="CleanUrlInterceptor.html" class="el_class">CleanUrlInterceptor</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="160" alt="160"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="16" alt="16"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">13</td><td class="ctr2" id="g8">13</td><td class="ctr1" id="h8">27</td><td class="ctr2" id="i9">27</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a11"><a href="InitialSetup.html" class="el_class">InitialSetup</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="158" alt="158"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="12" alt="12"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f6">14</td><td class="ctr2" id="g7">14</td><td class="ctr1" id="h6">51</td><td class="ctr2" id="i6">51</td><td class="ctr1" id="j6">8</td><td class="ctr2" id="k6">8</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a21"><a href="RuntimePathConfig.html" class="el_class">RuntimePathConfig</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="149" alt="149"/></td><td class="ctr2" id="c2">2%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="16" alt="16"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f9">11</td><td class="ctr2" id="g9">12</td><td class="ctr1" id="h7">30</td><td class="ctr2" id="i8">31</td><td class="ctr1" id="j12">3</td><td class="ctr2" id="k10">4</td><td class="ctr1" id="l24">0</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a12"><a href="InstallationPathConfig.html" class="el_class">InstallationPathConfig</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="84" alt="84"/><img src="../jacoco-resources/greenbar.gif" width="7" height="10" title="50" alt="50"/></td><td class="ctr2" id="c1">37%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="5" alt="5"/></td><td class="ctr2" id="e0">16%</td><td class="ctr1" id="f8">13</td><td class="ctr2" id="g6">16</td><td class="ctr1" id="h11">22</td><td class="ctr2" id="i7">37</td><td class="ctr1" id="j5">10</td><td class="ctr2" id="k3">13</td><td class="ctr1" id="l25">0</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a13"><a href="LocaleConfiguration.html" class="el_class">LocaleConfiguration</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="80" alt="80"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="8" alt="8"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f10">8</td><td class="ctr2" id="g10">8</td><td class="ctr1" id="h9">24</td><td class="ctr2" id="i10">24</td><td class="ctr1" id="j10">4</td><td class="ctr2" id="k11">4</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a18"><a href="OpenApiConfig.html" class="el_class">OpenApiConfig</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="76" alt="76"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f16">4</td><td class="ctr2" id="g16">4</td><td class="ctr1" id="h10">24</td><td class="ctr2" id="i11">24</td><td class="ctr1" id="j17">2</td><td class="ctr2" id="k17">2</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a10"><a href="FileFallbackTemplateResolver.html" class="el_class">FileFallbackTemplateResolver</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="48" alt="48"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">5</td><td class="ctr2" id="g12">5</td><td class="ctr1" id="h12">17</td><td class="ctr2" id="i12">17</td><td class="ctr1" id="j18">2</td><td class="ctr2" id="k18">2</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a17"><a href="MetricsFilter.html" class="el_class">MetricsFilter</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="48" alt="48"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f17">4</td><td class="ctr2" id="g17">4</td><td class="ctr1" id="h13">16</td><td class="ctr2" id="i13">16</td><td class="ctr1" id="j19">2</td><td class="ctr2" id="k19">2</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a9"><a href="ExternalAppDepConfig$1.html" class="el_class">ExternalAppDepConfig.new HashMap() {...}</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="46" alt="46"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g26">1</td><td class="ctr1" id="h19">7</td><td class="ctr2" id="i19">7</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k26">1</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a20"><a href="PostHogLoggerImpl.html" class="el_class">PostHogLoggerImpl</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="36" alt="36"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f11">8</td><td class="ctr2" id="g11">8</td><td class="ctr1" id="h14">14</td><td class="ctr2" id="i14">14</td><td class="ctr1" id="j7">7</td><td class="ctr2" id="k7">7</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m15">1</td></tr><tr><td id="a23"><a href="WebMvcConfig.html" class="el_class">WebMvcConfig</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="34" alt="34"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f19">3</td><td class="ctr2" id="g19">3</td><td class="ctr1" id="h16">9</td><td class="ctr2" id="i16">9</td><td class="ctr1" id="j13">3</td><td class="ctr2" id="k13">3</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m16">1</td></tr><tr><td id="a19"><a href="PostHogConfig.html" class="el_class">PostHogConfig</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="32" alt="32"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d17"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f13">5</td><td class="ctr2" id="g13">5</td><td class="ctr1" id="h15">10</td><td class="ctr2" id="i15">10</td><td class="ctr1" id="j11">4</td><td class="ctr2" id="k12">4</td><td class="ctr1" id="l15">1</td><td class="ctr2" id="m17">1</td></tr><tr><td id="a1"><a href="AppUpdateService.html" class="el_class">AppUpdateService</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="31" alt="31"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f14">5</td><td class="ctr2" id="g14">5</td><td class="ctr1" id="h20">7</td><td class="ctr2" id="i20">7</td><td class="ctr1" id="j20">2</td><td class="ctr2" id="k20">2</td><td class="ctr1" id="l16">1</td><td class="ctr2" id="m18">1</td></tr><tr><td id="a7"><a href="EnterpriseEndpointFilter.html" class="el_class">EnterpriseEndpointFilter</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="28" alt="28"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e17">0%</td><td class="ctr1" id="f15">5</td><td class="ctr2" id="g15">5</td><td class="ctr1" id="h17">9</td><td class="ctr2" id="i17">9</td><td class="ctr1" id="j14">3</td><td class="ctr2" id="k14">3</td><td class="ctr1" id="l17">1</td><td class="ctr2" id="m19">1</td></tr><tr><td id="a26"><a href="YamlPropertySourceFactory.html" class="el_class">YamlPropertySourceFactory</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="27" alt="27"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">2</td><td class="ctr2" id="g22">2</td><td class="ctr1" id="h21">6</td><td class="ctr2" id="i21">6</td><td class="ctr1" id="j21">2</td><td class="ctr2" id="k21">2</td><td class="ctr1" id="l18">1</td><td class="ctr2" id="m20">1</td></tr><tr><td id="a6"><a href="EndpointInterceptor.html" class="el_class">EndpointInterceptor</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="26" alt="26"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d18"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e18">0%</td><td class="ctr1" id="f18">4</td><td class="ctr2" id="g18">4</td><td class="ctr1" id="h18">9</td><td class="ctr2" id="i18">9</td><td class="ctr1" id="j15">3</td><td class="ctr2" id="k15">3</td><td class="ctr1" id="l19">1</td><td class="ctr2" id="m21">1</td></tr><tr><td id="a25"><a href="YamlHelper$1.html" class="el_class">YamlHelper.new StreamDataWriter() {...}</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="21" alt="21"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f20">3</td><td class="ctr2" id="g20">3</td><td class="ctr1" id="h22">5</td><td class="ctr2" id="i22">5</td><td class="ctr1" id="j16">3</td><td class="ctr2" id="k16">3</td><td class="ctr1" id="l20">1</td><td class="ctr2" id="m22">1</td></tr><tr><td id="a16"><a href="MetricsConfig$1.html" class="el_class">MetricsConfig.new MeterFilter() {...}</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="15" alt="15"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d19"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e19">0%</td><td class="ctr1" id="f21">3</td><td class="ctr2" id="g21">3</td><td class="ctr1" id="h23">4</td><td class="ctr2" id="i23">4</td><td class="ctr1" id="j22">2</td><td class="ctr2" id="k22">2</td><td class="ctr1" id="l21">1</td><td class="ctr2" id="m23">1</td></tr><tr><td id="a15"><a href="MetricsConfig.html" class="el_class">MetricsConfig</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="8" alt="8"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f23">2</td><td class="ctr2" id="g23">2</td><td class="ctr1" id="h25">2</td><td class="ctr2" id="i25">2</td><td class="ctr1" id="j23">2</td><td class="ctr2" id="k23">2</td><td class="ctr1" id="l22">1</td><td class="ctr2" id="m24">1</td></tr><tr><td id="a22"><a href="StartupApplicationListener.html" class="el_class">StartupApplicationListener</a></td><td class="bar" id="b25"/><td class="ctr2" id="c26">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f24">2</td><td class="ctr2" id="g24">2</td><td class="ctr1" id="h24">3</td><td class="ctr2" id="i24">3</td><td class="ctr1" id="j24">2</td><td class="ctr2" id="k24">2</td><td class="ctr1" id="l23">1</td><td class="ctr2" id="m25">1</td></tr><tr><td id="a14"><a href="LogbackPropertyLoader.html" class="el_class">LogbackPropertyLoader</a></td><td class="bar" id="b26"/><td class="ctr2" id="c0">100%</td><td class="bar" id="d26"/><td class="ctr2" id="e26">n/a</td><td class="ctr1" id="f26">0</td><td class="ctr2" id="g25">2</td><td class="ctr1" id="h26">0</td><td class="ctr2" id="i26">2</td><td class="ctr1" id="j26">0</td><td class="ctr2" id="k25">2</td><td class="ctr1" id="l26">0</td><td class="ctr2" id="m26">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.config/index.source.html b/testResult/html/stirling.software.SPDF.config/index.source.html
    new file mode 100644
    index 000000000..1763b1caf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.config/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.config</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.config</span></div><h1>stirling.software.SPDF.config</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4,187 of 4,246</td><td class="ctr2">1%</td><td class="bar">423 of 424</td><td class="ctr2">0%</td><td class="ctr1">376</td><td class="ctr2">382</td><td class="ctr1">989</td><td class="ctr2">1,007</td><td class="ctr1">164</td><td class="ctr2">170</td><td class="ctr1">24</td><td class="ctr2">27</td></tr></tfoot><tbody><tr><td id="a22"><a href="YamlHelper.java.html" class="el_source">YamlHelper.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="856" alt="856"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="152" alt="152"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">106</td><td class="ctr2" id="g0">106</td><td class="ctr1" id="h1">188</td><td class="ctr2" id="i1">188</td><td class="ctr1" id="j0">30</td><td class="ctr2" id="k0">30</td><td class="ctr1" id="l0">2</td><td class="ctr2" id="m0">2</td></tr><tr><td id="a4"><a href="EndpointConfiguration.java.html" class="el_source">EndpointConfiguration.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="112" height="10" title="802" alt="802"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="42" alt="42"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">36</td><td class="ctr2" id="g3">36</td><td class="ctr1" id="h0">200</td><td class="ctr2" id="i0">200</td><td class="ctr1" id="j2">15</td><td class="ctr2" id="k2">15</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a3"><a href="ConfigInitializer.java.html" class="el_source">ConfigInitializer.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="58" height="10" title="417" alt="417"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="24" alt="24"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f5">16</td><td class="ctr2" id="g5">16</td><td class="ctr1" id="h4">65</td><td class="ctr2" id="i4">65</td><td class="ctr1" id="j9">4</td><td class="ctr2" id="k9">4</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a5"><a href="EndpointInspector.java.html" class="el_source">EndpointInspector.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="57" height="10" title="411" alt="411"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="62" alt="62"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f1">42</td><td class="ctr2" id="g1">42</td><td class="ctr1" id="h2">111</td><td class="ctr2" id="i2">111</td><td class="ctr1" id="j3">11</td><td class="ctr2" id="k4">11</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a8"><a href="ExternalAppDepConfig.java.html" class="el_source">ExternalAppDepConfig.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="404" alt="404"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="28" alt="28"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">25</td><td class="ctr2" id="g4">25</td><td class="ctr1" id="h3">85</td><td class="ctr2" id="i3">85</td><td class="ctr1" id="j4">11</td><td class="ctr2" id="k5">11</td><td class="ctr1" id="l1">2</td><td class="ctr2" id="m1">2</td></tr><tr><td id="a0"><a href="AppConfig.java.html" class="el_source">AppConfig.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="251" alt="251"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="26" alt="26"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f2">40</td><td class="ctr2" id="g2">40</td><td class="ctr1" id="h5">57</td><td class="ctr2" id="i5">57</td><td class="ctr1" id="j1">27</td><td class="ctr2" id="k1">27</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a2"><a href="CleanUrlInterceptor.java.html" class="el_source">CleanUrlInterceptor.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="160" alt="160"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="16" alt="16"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">13</td><td class="ctr2" id="g8">13</td><td class="ctr1" id="h8">27</td><td class="ctr2" id="i9">27</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a10"><a href="InitialSetup.java.html" class="el_source">InitialSetup.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="158" alt="158"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="12" alt="12"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f6">14</td><td class="ctr2" id="g7">14</td><td class="ctr1" id="h6">51</td><td class="ctr2" id="i6">51</td><td class="ctr1" id="j6">8</td><td class="ctr2" id="k6">8</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a19"><a href="RuntimePathConfig.java.html" class="el_source">RuntimePathConfig.java</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="149" alt="149"/></td><td class="ctr2" id="c2">2%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="16" alt="16"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f9">11</td><td class="ctr2" id="g9">12</td><td class="ctr1" id="h7">30</td><td class="ctr2" id="i8">31</td><td class="ctr1" id="j13">3</td><td class="ctr2" id="k10">4</td><td class="ctr1" id="l21">0</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a11"><a href="InstallationPathConfig.java.html" class="el_source">InstallationPathConfig.java</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="84" alt="84"/><img src="../jacoco-resources/greenbar.gif" width="7" height="10" title="50" alt="50"/></td><td class="ctr2" id="c1">37%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="5" alt="5"/></td><td class="ctr2" id="e0">16%</td><td class="ctr1" id="f8">13</td><td class="ctr2" id="g6">16</td><td class="ctr1" id="h11">22</td><td class="ctr2" id="i7">37</td><td class="ctr1" id="j5">10</td><td class="ctr2" id="k3">13</td><td class="ctr1" id="l22">0</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a12"><a href="LocaleConfiguration.java.html" class="el_source">LocaleConfiguration.java</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="80" alt="80"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="8" alt="8"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f10">8</td><td class="ctr2" id="g10">8</td><td class="ctr1" id="h9">24</td><td class="ctr2" id="i10">24</td><td class="ctr1" id="j10">4</td><td class="ctr2" id="k11">4</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a16"><a href="OpenApiConfig.java.html" class="el_source">OpenApiConfig.java</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="76" alt="76"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f17">4</td><td class="ctr2" id="g17">4</td><td class="ctr1" id="h10">24</td><td class="ctr2" id="i11">24</td><td class="ctr1" id="j17">2</td><td class="ctr2" id="k17">2</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a9"><a href="FileFallbackTemplateResolver.java.html" class="el_source">FileFallbackTemplateResolver.java</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="48" alt="48"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">5</td><td class="ctr2" id="g12">5</td><td class="ctr1" id="h12">17</td><td class="ctr2" id="i12">17</td><td class="ctr1" id="j18">2</td><td class="ctr2" id="k18">2</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a15"><a href="MetricsFilter.java.html" class="el_source">MetricsFilter.java</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="48" alt="48"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f18">4</td><td class="ctr2" id="g18">4</td><td class="ctr1" id="h13">16</td><td class="ctr2" id="i13">16</td><td class="ctr1" id="j19">2</td><td class="ctr2" id="k19">2</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a18"><a href="PostHogLoggerImpl.java.html" class="el_source">PostHogLoggerImpl.java</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="36" alt="36"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f11">8</td><td class="ctr2" id="g11">8</td><td class="ctr1" id="h14">14</td><td class="ctr2" id="i14">14</td><td class="ctr1" id="j7">7</td><td class="ctr2" id="k7">7</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m15">1</td></tr><tr><td id="a21"><a href="WebMvcConfig.java.html" class="el_source">WebMvcConfig.java</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="34" alt="34"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">3</td><td class="ctr2" id="g20">3</td><td class="ctr1" id="h16">9</td><td class="ctr2" id="i16">9</td><td class="ctr1" id="j14">3</td><td class="ctr2" id="k14">3</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m16">1</td></tr><tr><td id="a17"><a href="PostHogConfig.java.html" class="el_source">PostHogConfig.java</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="32" alt="32"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f13">5</td><td class="ctr2" id="g13">5</td><td class="ctr1" id="h15">10</td><td class="ctr2" id="i15">10</td><td class="ctr1" id="j11">4</td><td class="ctr2" id="k12">4</td><td class="ctr1" id="l15">1</td><td class="ctr2" id="m17">1</td></tr><tr><td id="a1"><a href="AppUpdateService.java.html" class="el_source">AppUpdateService.java</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="31" alt="31"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f14">5</td><td class="ctr2" id="g14">5</td><td class="ctr1" id="h19">7</td><td class="ctr2" id="i19">7</td><td class="ctr1" id="j20">2</td><td class="ctr2" id="k20">2</td><td class="ctr1" id="l16">1</td><td class="ctr2" id="m18">1</td></tr><tr><td id="a7"><a href="EnterpriseEndpointFilter.java.html" class="el_source">EnterpriseEndpointFilter.java</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="28" alt="28"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e17">0%</td><td class="ctr1" id="f15">5</td><td class="ctr2" id="g15">5</td><td class="ctr1" id="h17">9</td><td class="ctr2" id="i17">9</td><td class="ctr1" id="j15">3</td><td class="ctr2" id="k15">3</td><td class="ctr1" id="l17">1</td><td class="ctr2" id="m19">1</td></tr><tr><td id="a23"><a href="YamlPropertySourceFactory.java.html" class="el_source">YamlPropertySourceFactory.java</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="27" alt="27"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">2</td><td class="ctr2" id="g21">2</td><td class="ctr1" id="h20">6</td><td class="ctr2" id="i20">6</td><td class="ctr1" id="j21">2</td><td class="ctr2" id="k21">2</td><td class="ctr1" id="l18">1</td><td class="ctr2" id="m20">1</td></tr><tr><td id="a6"><a href="EndpointInterceptor.java.html" class="el_source">EndpointInterceptor.java</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="26" alt="26"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d18"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e18">0%</td><td class="ctr1" id="f19">4</td><td class="ctr2" id="g19">4</td><td class="ctr1" id="h18">9</td><td class="ctr2" id="i18">9</td><td class="ctr1" id="j16">3</td><td class="ctr2" id="k16">3</td><td class="ctr1" id="l19">1</td><td class="ctr2" id="m21">1</td></tr><tr><td id="a14"><a href="MetricsConfig.java.html" class="el_source">MetricsConfig.java</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="23" alt="23"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d19"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e19">0%</td><td class="ctr1" id="f16">5</td><td class="ctr2" id="g16">5</td><td class="ctr1" id="h21">5</td><td class="ctr2" id="i21">5</td><td class="ctr1" id="j12">4</td><td class="ctr2" id="k13">4</td><td class="ctr1" id="l2">2</td><td class="ctr2" id="m2">2</td></tr><tr><td id="a20"><a href="StartupApplicationListener.java.html" class="el_source">StartupApplicationListener.java</a></td><td class="bar" id="b22"/><td class="ctr2" id="c23">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">2</td><td class="ctr2" id="g22">2</td><td class="ctr1" id="h22">3</td><td class="ctr2" id="i22">3</td><td class="ctr1" id="j22">2</td><td class="ctr2" id="k22">2</td><td class="ctr1" id="l20">1</td><td class="ctr2" id="m22">1</td></tr><tr><td id="a13"><a href="LogbackPropertyLoader.java.html" class="el_source">LogbackPropertyLoader.java</a></td><td class="bar" id="b23"/><td class="ctr2" id="c0">100%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">0</td><td class="ctr2" id="g23">2</td><td class="ctr1" id="h23">0</td><td class="ctr2" id="i23">2</td><td class="ctr1" id="j23">0</td><td class="ctr2" id="k23">2</td><td class="ctr1" id="l23">0</td><td class="ctr2" id="m23">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertHtmlToPDF.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertHtmlToPDF.html
    new file mode 100644
    index 000000000..58a4a08d3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertHtmlToPDF.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertHtmlToPDF</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ConvertHtmlToPDF</span></div><h1>ConvertHtmlToPDF</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">73 of 73</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">22</td><td class="ctr2">22</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="ConvertHtmlToPDF.java.html#L52" class="el_method">HtmlToPdf(HTMLToPdfRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="61" alt="61"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">17</td><td class="ctr2" id="i0">17</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ConvertHtmlToPDF.java.html#L37" class="el_method">ConvertHtmlToPDF(CustomPDFDocumentFactory, ApplicationProperties, RuntimePathConfig)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertHtmlToPDF.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertHtmlToPDF.java.html
    new file mode 100644
    index 000000000..3a8b57c36
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertHtmlToPDF.java.html
    @@ -0,0 +1,85 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertHtmlToPDF.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ConvertHtmlToPDF.java</span></div><h1>ConvertHtmlToPDF.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.config.RuntimePathConfig;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.FileToPdf;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +public class ConvertHtmlToPDF {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    private final RuntimePathConfig runtimePathConfig;
    +
    +    @Autowired
    +    public ConvertHtmlToPDF(
    +            CustomPDFDocumentFactory pdfDocumentFactory,
    +            ApplicationProperties applicationProperties,
    +<span class="nc" id="L37">            RuntimePathConfig runtimePathConfig) {</span>
    +<span class="nc" id="L38">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +
    +<span class="nc" id="L40">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L41">        this.runtimePathConfig = runtimePathConfig;</span>
    +<span class="nc" id="L42">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/html/pdf&quot;)
    +    @Operation(
    +            summary = &quot;Convert an HTML or ZIP (containing HTML and CSS) to PDF&quot;,
    +            description =
    +                    &quot;This endpoint takes an HTML or ZIP file input and converts it to a PDF format.&quot;
    +                            + &quot; Input:HTML Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; HtmlToPdf(@ModelAttribute HTMLToPdfRequest request)
    +            throws Exception {
    +<span class="nc" id="L52">        MultipartFile fileInput = request.getFileInput();</span>
    +
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">        if (fileInput == null) {</span>
    +<span class="nc" id="L55">            throw new IllegalArgumentException(</span>
    +                    &quot;Please provide an HTML or ZIP file for conversion.&quot;);
    +        }
    +
    +<span class="nc" id="L59">        String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename());</span>
    +<span class="nc bnc" id="L60" title="All 2 branches missed.">        if (originalFilename == null</span>
    +<span class="nc bnc" id="L61" title="All 4 branches missed.">                || (!originalFilename.endsWith(&quot;.html&quot;) &amp;&amp; !originalFilename.endsWith(&quot;.zip&quot;))) {</span>
    +<span class="nc" id="L62">            throw new IllegalArgumentException(&quot;File must be either .html or .zip format.&quot;);</span>
    +        }
    +
    +<span class="nc" id="L65">        boolean disableSanitize =</span>
    +<span class="nc" id="L66">                Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());</span>
    +
    +<span class="nc" id="L68">        byte[] pdfBytes =</span>
    +<span class="nc" id="L69">                FileToPdf.convertHtmlToPdf(</span>
    +<span class="nc" id="L70">                        runtimePathConfig.getWeasyPrintPath(),</span>
    +                        request,
    +<span class="nc" id="L72">                        fileInput.getBytes(),</span>
    +                        originalFilename,
    +                        disableSanitize);
    +
    +<span class="nc" id="L76">        pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);</span>
    +
    +<span class="nc" id="L78">        String outputFilename =</span>
    +<span class="nc" id="L79">                originalFilename.replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;.pdf&quot;; // Remove file extension and append .pdf
    +
    +<span class="nc" id="L82">        return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertImgPDFController.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertImgPDFController.html
    new file mode 100644
    index 000000000..83d025a11
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertImgPDFController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertImgPDFController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ConvertImgPDFController</span></div><h1>ConvertImgPDFController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">487 of 487</td><td class="ctr2">0%</td><td class="bar">56 of 56</td><td class="ctr2">0%</td><td class="ctr1">35</td><td class="ctr2">35</td><td class="ctr1">131</td><td class="ctr2">131</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a1"><a href="ConvertImgPDFController.java.html#L61" class="el_method">convertToImage(ConvertToImageRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="340" alt="340"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="40" alt="40"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">21</td><td class="ctr2" id="g0">21</td><td class="ctr1" id="h0">96</td><td class="ctr2" id="i0">96</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="ConvertImgPDFController.java.html#L252" class="el_method">rearrangePdfPages(MultipartFile, String[])</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="75" alt="75"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h1">17</td><td class="ctr2" id="i1">17</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="ConvertImgPDFController.java.html#L217" class="el_method">convertToPdf(ConvertToPdfRequest)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="45" alt="45"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h2">12</td><td class="ctr2" id="i2">12</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="ConvertImgPDFController.java.html#L237" class="el_method">getMediaType(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="12" alt="12"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="ConvertImgPDFController.java.html#L48" class="el_method">ConvertImgPDFController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a4"><a href="ConvertImgPDFController.java.html#L147" class="el_method">lambda$convertToImage$0(Path)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="5" alt="5"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="ConvertImgPDFController.java.html#L41" class="el_method">static {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertImgPDFController.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertImgPDFController.java.html
    new file mode 100644
    index 000000000..23aa89dd6
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertImgPDFController.java.html
    @@ -0,0 +1,284 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertImgPDFController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ConvertImgPDFController.java</span></div><h1>ConvertImgPDFController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.net.URLConnection;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.commons.io.FileUtils;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.rendering.ImageType;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.converters.ConvertToImageRequest;
    +import stirling.software.SPDF.model.api.converters.ConvertToPdfRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.*;
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +<span class="nc" id="L41">@Slf4j</span>
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +public class ConvertImgPDFController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L48">    public ConvertImgPDFController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L49">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L50">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf/img&quot;)
    +    @Operation(
    +            summary = &quot;Convert PDF to image(s)&quot;,
    +            description =
    +                    &quot;This endpoint converts a PDF file to image(s) with the specified image format,&quot;
    +                            + &quot; color type, and DPI. Users can choose to get a single image or multiple&quot;
    +                            + &quot; images.  Input:PDF Output:Image Type:SI-Conditional&quot;)
    +    public ResponseEntity&lt;byte[]&gt; convertToImage(@ModelAttribute ConvertToImageRequest request)
    +            throws NumberFormatException, Exception {
    +<span class="nc" id="L61">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L62">        String imageFormat = request.getImageFormat();</span>
    +<span class="nc" id="L63">        String singleOrMultiple = request.getSingleOrMultiple();</span>
    +<span class="nc" id="L64">        String colorType = request.getColorType();</span>
    +<span class="nc" id="L65">        String dpi = request.getDpi();</span>
    +<span class="nc" id="L66">        String pageNumbers = request.getPageNumbers();</span>
    +<span class="nc" id="L67">        Path tempFile = null;</span>
    +<span class="nc" id="L68">        Path tempOutputDir = null;</span>
    +<span class="nc" id="L69">        Path tempPdfPath = null;</span>
    +<span class="nc" id="L70">        byte[] result = null;</span>
    +        String[] pageOrderArr =
    +<span class="nc bnc" id="L72" title="All 4 branches missed.">                (pageNumbers != null &amp;&amp; !pageNumbers.trim().isEmpty())</span>
    +<span class="nc" id="L73">                        ? pageNumbers.split(&quot;,&quot;)</span>
    +<span class="nc" id="L74">                        : new String[] {&quot;all&quot;};</span>
    +        ;
    +        try {
    +            // Load the input PDF
    +<span class="nc" id="L78">            byte[] newPdfBytes = rearrangePdfPages(file, pageOrderArr);</span>
    +
    +<span class="nc" id="L80">            ImageType colorTypeResult = ImageType.RGB;</span>
    +<span class="nc bnc" id="L81" title="All 2 branches missed.">            if (&quot;greyscale&quot;.equals(colorType)) {</span>
    +<span class="nc" id="L82">                colorTypeResult = ImageType.GRAY;</span>
    +<span class="nc bnc" id="L83" title="All 2 branches missed.">            } else if (&quot;blackwhite&quot;.equals(colorType)) {</span>
    +<span class="nc" id="L84">                colorTypeResult = ImageType.BINARY;</span>
    +            }
    +            // returns bytes for image
    +<span class="nc" id="L87">            boolean singleImage = &quot;single&quot;.equals(singleOrMultiple);</span>
    +<span class="nc" id="L88">            String filename =</span>
    +<span class="nc" id="L89">                    Filenames.toSimpleFileName(file.getOriginalFilename())</span>
    +<span class="nc" id="L90">                            .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +
    +<span class="nc" id="L92">            result =</span>
    +<span class="nc" id="L93">                    PdfUtils.convertFromPdf(</span>
    +                            pdfDocumentFactory,
    +                            newPdfBytes,
    +<span class="nc bnc" id="L96" title="All 2 branches missed.">                            &quot;webp&quot;.equalsIgnoreCase(imageFormat)</span>
    +<span class="nc" id="L97">                                    ? &quot;png&quot;</span>
    +<span class="nc" id="L98">                                    : imageFormat.toUpperCase(),</span>
    +                            colorTypeResult,
    +                            singleImage,
    +<span class="nc" id="L101">                            Integer.valueOf(dpi),</span>
    +                            filename);
    +<span class="nc bnc" id="L103" title="All 4 branches missed.">            if (result == null || result.length == 0) {</span>
    +<span class="nc" id="L104">                log.error(&quot;resultant bytes for {} is null, error converting &quot;, filename);</span>
    +            }
    +<span class="nc bnc" id="L106" title="All 4 branches missed.">            if (&quot;webp&quot;.equalsIgnoreCase(imageFormat) &amp;&amp; !CheckProgramInstall.isPythonAvailable()) {</span>
    +<span class="nc" id="L107">                throw new IOException(&quot;Python is not installed. Required for WebP conversion.&quot;);</span>
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">            } else if (&quot;webp&quot;.equalsIgnoreCase(imageFormat)</span>
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">                    &amp;&amp; CheckProgramInstall.isPythonAvailable()) {</span>
    +                // Write the output stream to a temp file
    +<span class="nc" id="L111">                tempFile = Files.createTempFile(&quot;temp_png&quot;, &quot;.png&quot;);</span>
    +<span class="nc" id="L112">                try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) {</span>
    +<span class="nc" id="L113">                    fos.write(result);</span>
    +<span class="nc" id="L114">                    fos.flush();</span>
    +                }
    +
    +<span class="nc" id="L117">                String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();</span>
    +
    +<span class="nc" id="L119">                List&lt;String&gt; command = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L120">                command.add(pythonVersion);</span>
    +<span class="nc" id="L121">                command.add(&quot;./scripts/png_to_webp.py&quot;); // Python script to handle the conversion</span>
    +
    +                // Create a temporary directory for the output WebP files
    +<span class="nc" id="L124">                tempOutputDir = Files.createTempDirectory(&quot;webp_output&quot;);</span>
    +<span class="nc bnc" id="L125" title="All 2 branches missed.">                if (singleImage) {</span>
    +                    // Run the Python script to convert PNG to WebP
    +<span class="nc" id="L127">                    command.add(tempFile.toString());</span>
    +<span class="nc" id="L128">                    command.add(tempOutputDir.toString());</span>
    +<span class="nc" id="L129">                    command.add(&quot;--single&quot;);</span>
    +                } else {
    +                    // Save the uploaded PDF to a temporary file
    +<span class="nc" id="L132">                    tempPdfPath = Files.createTempFile(&quot;temp_pdf&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L133">                    file.transferTo(tempPdfPath.toFile());</span>
    +                    // Run the Python script to convert PDF to WebP
    +<span class="nc" id="L135">                    command.add(tempPdfPath.toString());</span>
    +<span class="nc" id="L136">                    command.add(tempOutputDir.toString());</span>
    +                }
    +<span class="nc" id="L138">                command.add(&quot;--dpi&quot;);</span>
    +<span class="nc" id="L139">                command.add(dpi);</span>
    +<span class="nc" id="L140">                ProcessExecutorResult resultProcess =</span>
    +<span class="nc" id="L141">                        ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)</span>
    +<span class="nc" id="L142">                                .runCommandWithOutputHandling(command);</span>
    +
    +                // Find all WebP files in the output directory
    +<span class="nc" id="L145">                List&lt;Path&gt; webpFiles =</span>
    +<span class="nc" id="L146">                        Files.walk(tempOutputDir)</span>
    +<span class="nc" id="L147">                                .filter(path -&gt; path.toString().endsWith(&quot;.webp&quot;))</span>
    +<span class="nc" id="L148">                                .toList();</span>
    +
    +<span class="nc bnc" id="L150" title="All 2 branches missed.">                if (webpFiles.isEmpty()) {</span>
    +<span class="nc" id="L151">                    log.error(&quot;No WebP files were created in: {}&quot;, tempOutputDir.toString());</span>
    +<span class="nc" id="L152">                    throw new IOException(</span>
    +<span class="nc" id="L153">                            &quot;No WebP files were created. &quot; + resultProcess.getMessages());</span>
    +                }
    +
    +<span class="nc" id="L156">                byte[] bodyBytes = new byte[0];</span>
    +
    +<span class="nc bnc" id="L158" title="All 2 branches missed.">                if (webpFiles.size() == 1) {</span>
    +                    // Return the single WebP file directly
    +<span class="nc" id="L160">                    Path webpFilePath = webpFiles.get(0);</span>
    +<span class="nc" id="L161">                    bodyBytes = Files.readAllBytes(webpFilePath);</span>
    +<span class="nc" id="L162">                } else {</span>
    +                    // Create a ZIP file containing all WebP images
    +<span class="nc" id="L164">                    ByteArrayOutputStream zipOutputStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L165">                    try (ZipOutputStream zos = new ZipOutputStream(zipOutputStream)) {</span>
    +<span class="nc bnc" id="L166" title="All 2 branches missed.">                        for (Path webpFile : webpFiles) {</span>
    +<span class="nc" id="L167">                            zos.putNextEntry(new ZipEntry(webpFile.getFileName().toString()));</span>
    +<span class="nc" id="L168">                            Files.copy(webpFile, zos);</span>
    +<span class="nc" id="L169">                            zos.closeEntry();</span>
    +<span class="nc" id="L170">                        }</span>
    +                    }
    +<span class="nc" id="L172">                    bodyBytes = zipOutputStream.toByteArray();</span>
    +                }
    +                // Clean up the temporary files
    +<span class="nc" id="L175">                Files.deleteIfExists(tempFile);</span>
    +<span class="nc bnc" id="L176" title="All 2 branches missed.">                if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());</span>
    +<span class="nc" id="L177">                result = bodyBytes;</span>
    +            }
    +
    +<span class="nc bnc" id="L180" title="All 2 branches missed.">            if (singleImage) {</span>
    +<span class="nc" id="L181">                String docName = filename + &quot;.&quot; + imageFormat;</span>
    +<span class="nc" id="L182">                MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));</span>
    +<span class="nc" id="L183">                return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);</span>
    +            } else {
    +<span class="nc" id="L185">                String zipFilename = filename + &quot;_convertedToImages.zip&quot;;</span>
    +<span class="nc" id="L186">                return WebResponseUtils.bytesToWebResponse(</span>
    +                        result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
    +            }
    +
    +        } finally {
    +            try {
    +                // Clean up temporary files
    +<span class="nc bnc" id="L193" title="All 2 branches missed.">                if (tempFile != null) {</span>
    +<span class="nc" id="L194">                    Files.deleteIfExists(tempFile);</span>
    +                }
    +<span class="nc bnc" id="L196" title="All 2 branches missed.">                if (tempPdfPath != null) {</span>
    +<span class="nc" id="L197">                    Files.deleteIfExists(tempPdfPath);</span>
    +                }
    +<span class="nc bnc" id="L199" title="All 2 branches missed.">                if (tempOutputDir != null) {</span>
    +<span class="nc" id="L200">                    FileUtils.deleteDirectory(tempOutputDir.toFile());</span>
    +                }
    +<span class="nc" id="L202">            } catch (Exception e) {</span>
    +<span class="nc" id="L203">                log.error(&quot;Error cleaning up temporary files&quot;, e);</span>
    +<span class="nc" id="L204">            }</span>
    +        }
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/img/pdf&quot;)
    +    @Operation(
    +            summary = &quot;Convert images to a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint converts one or more images to a PDF file. Users can specify&quot;
    +                            + &quot; whether to stretch the images to fit the PDF page, and whether to&quot;
    +                            + &quot; automatically rotate the images. Input:Image Output:PDF Type:MISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; convertToPdf(@ModelAttribute ConvertToPdfRequest request)
    +            throws IOException {
    +<span class="nc" id="L217">        MultipartFile[] file = request.getFileInput();</span>
    +<span class="nc" id="L218">        String fitOption = request.getFitOption();</span>
    +<span class="nc" id="L219">        String colorType = request.getColorType();</span>
    +<span class="nc" id="L220">        boolean autoRotate = request.isAutoRotate();</span>
    +        // Handle Null entries for formdata
    +<span class="nc bnc" id="L222" title="All 4 branches missed.">        if (colorType == null || colorType.isBlank()) {</span>
    +<span class="nc" id="L223">            colorType = &quot;color&quot;;</span>
    +        }
    +<span class="nc bnc" id="L225" title="All 4 branches missed.">        if (fitOption == null || fitOption.isEmpty()) {</span>
    +<span class="nc" id="L226">            fitOption = &quot;fillPage&quot;;</span>
    +        }
    +        // Convert the file to PDF and get the resulting bytes
    +<span class="nc" id="L229">        byte[] bytes =</span>
    +<span class="nc" id="L230">                PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);</span>
    +<span class="nc" id="L231">        return WebResponseUtils.bytesToWebResponse(</span>
    +                bytes,
    +<span class="nc" id="L233">                file[0].getOriginalFilename().replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;) + &quot;_converted.pdf&quot;);</span>
    +    }
    +
    +    private String getMediaType(String imageFormat) {
    +<span class="nc" id="L237">        String mimeType = URLConnection.guessContentTypeFromName(&quot;.&quot; + imageFormat);</span>
    +<span class="nc bnc" id="L238" title="All 2 branches missed.">        return &quot;null&quot;.equals(mimeType) ? &quot;application/octet-stream&quot; : mimeType;</span>
    +    }
    +
    +    /**
    +     * Rearranges the pages of the given PDF document based on the specified page order.
    +     *
    +     * @param pdfBytes The byte array of the original PDF file.
    +     * @param pageOrderArr An array of page numbers indicating the new order.
    +     * @return A byte array of the rearranged PDF.
    +     * @throws IOException If an error occurs while processing the PDF.
    +     */
    +    private byte[] rearrangePdfPages(MultipartFile pdfFile, String[] pageOrderArr)
    +            throws IOException {
    +        // Load the input PDF
    +<span class="nc" id="L252">        PDDocument document = pdfDocumentFactory.load(pdfFile);</span>
    +<span class="nc" id="L253">        int totalPages = document.getNumberOfPages();</span>
    +<span class="nc" id="L254">        List&lt;Integer&gt; newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);</span>
    +
    +        // Create a new list to hold the pages in the new order
    +<span class="nc" id="L257">        List&lt;PDPage&gt; newPages = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L258" title="All 2 branches missed.">        for (int pageIndex : newPageOrder) {</span>
    +<span class="nc" id="L259">            newPages.add(document.getPage(pageIndex));</span>
    +<span class="nc" id="L260">        }</span>
    +
    +        // Remove all the pages from the original document
    +<span class="nc bnc" id="L263" title="All 2 branches missed.">        for (int i = document.getNumberOfPages() - 1; i &gt;= 0; i--) {</span>
    +<span class="nc" id="L264">            document.removePage(i);</span>
    +        }
    +
    +        // Add the pages in the new order
    +<span class="nc bnc" id="L268" title="All 2 branches missed.">        for (PDPage page : newPages) {</span>
    +<span class="nc" id="L269">            document.addPage(page);</span>
    +<span class="nc" id="L270">        }</span>
    +
    +        // Convert PDDocument to byte array
    +        byte[] newPdfBytes;
    +<span class="nc" id="L274">        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {</span>
    +<span class="nc" id="L275">            document.save(baos);</span>
    +<span class="nc" id="L276">            newPdfBytes = baos.toByteArray();</span>
    +        } finally {
    +<span class="nc" id="L278">            document.close();</span>
    +        }
    +
    +<span class="nc" id="L281">        return newPdfBytes;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.html
    new file mode 100644
    index 000000000..cb337dc23
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertMarkdownToPdf</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ConvertMarkdownToPdf</span></div><h1>ConvertMarkdownToPdf</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 100</td><td class="ctr2">100%</td><td class="bar">1 of 6</td><td class="ctr2">83%</td><td class="ctr1">1</td><td class="ctr2">6</td><td class="ctr1">0</td><td class="ctr2">29</td><td class="ctr1">0</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="ConvertMarkdownToPdf.java.html#L61" class="el_method">markdownToPdf(GeneralFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="84" alt="84"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="1" alt="1"/><img src="../jacoco-resources/greenbar.gif" width="100" height="10" title="5" alt="5"/></td><td class="ctr2" id="e0">83%</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">24</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ConvertMarkdownToPdf.java.html#L46" class="el_method">ConvertMarkdownToPdf(CustomPDFDocumentFactory, ApplicationProperties, RuntimePathConfig)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="17" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ConvertMarkdownToPdf.java.html#L79" class="el_method">lambda$markdownToPdf$0(AttributeProviderContext)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/greenbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">100%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">0</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">0</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.java.html
    new file mode 100644
    index 000000000..a82789065
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertMarkdownToPdf.java.html
    @@ -0,0 +1,111 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertMarkdownToPdf.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ConvertMarkdownToPdf.java</span></div><h1>ConvertMarkdownToPdf.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.commonmark.Extension;
    +import org.commonmark.ext.gfm.tables.TableBlock;
    +import org.commonmark.ext.gfm.tables.TablesExtension;
    +import org.commonmark.node.Node;
    +import org.commonmark.parser.Parser;
    +import org.commonmark.renderer.html.AttributeProvider;
    +import org.commonmark.renderer.html.HtmlRenderer;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.config.RuntimePathConfig;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.api.GeneralFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.FileToPdf;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +public class ConvertMarkdownToPdf {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    private final ApplicationProperties applicationProperties;
    +    private final RuntimePathConfig runtimePathConfig;
    +
    +    @Autowired
    +    public ConvertMarkdownToPdf(
    +            CustomPDFDocumentFactory pdfDocumentFactory,
    +            ApplicationProperties applicationProperties,
    +<span class="fc" id="L46">            RuntimePathConfig runtimePathConfig) {</span>
    +<span class="fc" id="L47">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +
    +<span class="fc" id="L49">        this.applicationProperties = applicationProperties;</span>
    +<span class="fc" id="L50">        this.runtimePathConfig = runtimePathConfig;</span>
    +<span class="fc" id="L51">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/markdown/pdf&quot;)
    +    @Operation(
    +            summary = &quot;Convert a Markdown file to PDF&quot;,
    +            description =
    +                    &quot;This endpoint takes a Markdown file input, converts it to HTML, and then to&quot;
    +                            + &quot; PDF format. Input:MARKDOWN Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; markdownToPdf(@ModelAttribute GeneralFile request)
    +            throws Exception {
    +<span class="fc" id="L61">        MultipartFile fileInput = request.getFileInput();</span>
    +
    +<span class="fc bfc" id="L63" title="All 2 branches covered.">        if (fileInput == null) {</span>
    +<span class="fc" id="L64">            throw new IllegalArgumentException(&quot;Please provide a Markdown file for conversion.&quot;);</span>
    +        }
    +
    +<span class="fc" id="L67">        String originalFilename = Filenames.toSimpleFileName(fileInput.getOriginalFilename());</span>
    +<span class="pc bpc" id="L68" title="1 of 4 branches missed.">        if (originalFilename == null || !originalFilename.endsWith(&quot;.md&quot;)) {</span>
    +<span class="fc" id="L69">            throw new IllegalArgumentException(&quot;File must be in .md format.&quot;);</span>
    +        }
    +
    +        // Convert Markdown to HTML using CommonMark
    +<span class="fc" id="L73">        List&lt;Extension&gt; extensions = List.of(TablesExtension.create());</span>
    +<span class="fc" id="L74">        Parser parser = Parser.builder().extensions(extensions).build();</span>
    +
    +<span class="fc" id="L76">        Node document = parser.parse(new String(fileInput.getBytes()));</span>
    +        HtmlRenderer renderer =
    +<span class="fc" id="L78">                HtmlRenderer.builder()</span>
    +<span class="fc" id="L79">                        .attributeProviderFactory(context -&gt; new TableAttributeProvider())</span>
    +<span class="fc" id="L80">                        .extensions(extensions)</span>
    +<span class="fc" id="L81">                        .build();</span>
    +
    +<span class="fc" id="L83">        String htmlContent = renderer.render(document);</span>
    +
    +<span class="fc" id="L85">        boolean disableSanitize =</span>
    +<span class="fc" id="L86">                Boolean.TRUE.equals(applicationProperties.getSystem().getDisableSanitize());</span>
    +
    +<span class="fc" id="L88">        byte[] pdfBytes =</span>
    +<span class="fc" id="L89">                FileToPdf.convertHtmlToPdf(</span>
    +<span class="fc" id="L90">                        runtimePathConfig.getWeasyPrintPath(),</span>
    +                        null,
    +<span class="fc" id="L92">                        htmlContent.getBytes(),</span>
    +                        &quot;converted.html&quot;,
    +                        disableSanitize);
    +<span class="fc" id="L95">        pdfBytes = pdfDocumentFactory.createNewBytesBasedOnOldDocument(pdfBytes);</span>
    +<span class="fc" id="L96">        String outputFilename =</span>
    +<span class="fc" id="L97">                originalFilename.replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;.pdf&quot;; // Remove file extension and append .pdf
    +<span class="fc" id="L99">        return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);</span>
    +    }
    +}
    +
    +<span class="fc" id="L103">class TableAttributeProvider implements AttributeProvider {</span>
    +    @Override
    +    public void setAttributes(Node node, String tagName, Map&lt;String, String&gt; attributes) {
    +<span class="fc bfc" id="L106" title="All 2 branches covered.">        if (node instanceof TableBlock) {</span>
    +<span class="fc" id="L107">            attributes.put(&quot;class&quot;, &quot;table table-striped&quot;);</span>
    +        }
    +<span class="fc" id="L109">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertOfficeController.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertOfficeController.html
    new file mode 100644
    index 000000000..c674ee819
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertOfficeController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertOfficeController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ConvertOfficeController</span></div><h1>ConvertOfficeController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">133 of 133</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">32</td><td class="ctr2">32</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="ConvertOfficeController.java.html#L49" class="el_method">convertToPdf(MultipartFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="87" alt="87"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">18</td><td class="ctr2" id="i0">18</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="ConvertOfficeController.java.html#L100" class="el_method">processFileToPDF(GeneralFile)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="31" alt="31"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">8</td><td class="ctr2" id="i1">8</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="ConvertOfficeController.java.html#L42" class="el_method">ConvertOfficeController(CustomPDFDocumentFactory, RuntimePathConfig)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="9" alt="9"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="ConvertOfficeController.java.html#L88" class="el_method">isValidFileExtension(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertOfficeController.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertOfficeController.java.html
    new file mode 100644
    index 000000000..17cc069b2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertOfficeController.java.html
    @@ -0,0 +1,118 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertOfficeController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ConvertOfficeController.java</span></div><h1>ConvertOfficeController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
    +
    +import org.apache.commons.io.FilenameUtils;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.config.RuntimePathConfig;
    +import stirling.software.SPDF.model.api.GeneralFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.ProcessExecutor;
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +public class ConvertOfficeController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +    private final RuntimePathConfig runtimePathConfig;
    +
    +    @Autowired
    +    public ConvertOfficeController(
    +<span class="nc" id="L42">            CustomPDFDocumentFactory pdfDocumentFactory, RuntimePathConfig runtimePathConfig) {</span>
    +<span class="nc" id="L43">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L44">        this.runtimePathConfig = runtimePathConfig;</span>
    +<span class="nc" id="L45">    }</span>
    +
    +    public File convertToPdf(MultipartFile inputFile) throws IOException, InterruptedException {
    +        // Check for valid file extension
    +<span class="nc" id="L49">        String originalFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename());</span>
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">        if (originalFilename == null</span>
    +<span class="nc bnc" id="L51" title="All 2 branches missed.">                || !isValidFileExtension(FilenameUtils.getExtension(originalFilename))) {</span>
    +<span class="nc" id="L52">            throw new IllegalArgumentException(&quot;Invalid file extension&quot;);</span>
    +        }
    +
    +        // Save the uploaded file to a temporary location
    +<span class="nc" id="L56">        Path tempInputFile =</span>
    +<span class="nc" id="L57">                Files.createTempFile(&quot;input_&quot;, &quot;.&quot; + FilenameUtils.getExtension(originalFilename));</span>
    +<span class="nc" id="L58">        inputFile.transferTo(tempInputFile);</span>
    +
    +        // Prepare the output file path
    +<span class="nc" id="L61">        Path tempOutputFile = Files.createTempFile(&quot;output_&quot;, &quot;.pdf&quot;);</span>
    +
    +        try {
    +            // Run the LibreOffice command
    +<span class="nc" id="L65">            List&lt;String&gt; command =</span>
    +                    new ArrayList&lt;&gt;(
    +<span class="nc" id="L67">                            Arrays.asList(</span>
    +<span class="nc" id="L68">                                    runtimePathConfig.getUnoConvertPath(),</span>
    +                                    &quot;--port&quot;,
    +                                    &quot;2003&quot;,
    +                                    &quot;--convert-to&quot;,
    +                                    &quot;pdf&quot;,
    +<span class="nc" id="L73">                                    tempInputFile.toString(),</span>
    +<span class="nc" id="L74">                                    tempOutputFile.toString()));</span>
    +<span class="nc" id="L75">            ProcessExecutorResult returnCode =</span>
    +<span class="nc" id="L76">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)</span>
    +<span class="nc" id="L77">                            .runCommandWithOutputHandling(command);</span>
    +
    +            // Read the converted PDF file
    +<span class="nc" id="L80">            return tempOutputFile.toFile();</span>
    +        } finally {
    +            // Clean up the temporary files
    +<span class="nc bnc" id="L83" title="All 2 branches missed.">            if (tempInputFile != null) Files.deleteIfExists(tempInputFile);</span>
    +        }
    +    }
    +
    +    private boolean isValidFileExtension(String fileExtension) {
    +<span class="nc" id="L88">        String extensionPattern = &quot;^(?i)[a-z0-9]{2,4}$&quot;;</span>
    +<span class="nc" id="L89">        return fileExtension.matches(extensionPattern);</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/file/pdf&quot;)
    +    @Operation(
    +            summary = &quot;Convert a file to a PDF using LibreOffice&quot;,
    +            description =
    +                    &quot;This endpoint converts a given file to a PDF using LibreOffice API  Input:ANY&quot;
    +                            + &quot; Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; processFileToPDF(@ModelAttribute GeneralFile request)
    +            throws Exception {
    +<span class="nc" id="L100">        MultipartFile inputFile = request.getFileInput();</span>
    +        // unused but can start server instance if startup time is to long
    +        // LibreOfficeListener.getInstance().start();
    +<span class="nc" id="L103">        File file = null;</span>
    +        try {
    +<span class="nc" id="L105">            file = convertToPdf(inputFile);</span>
    +
    +<span class="nc" id="L107">            PDDocument doc = pdfDocumentFactory.load(file);</span>
    +<span class="nc" id="L108">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +                    doc,
    +<span class="nc" id="L110">                    Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +<span class="nc" id="L111">                                    .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                            + &quot;_convertedToPDF.pdf&quot;);
    +        } finally {
    +<span class="nc bnc" id="L114" title="All 2 branches missed.">            if (file != null) file.delete();</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToHtml.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToHtml.html
    new file mode 100644
    index 000000000..75f96fe44
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToHtml.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertPDFToHtml</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ConvertPDFToHtml</span></div><h1>ConvertPDFToHtml</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="ConvertPDFToHtml.java.html#L28" class="el_method">processPdfToHTML(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="11" alt="11"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ConvertPDFToHtml.java.html#L19" class="el_method">ConvertPDFToHtml()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToHtml.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToHtml.java.html
    new file mode 100644
    index 000000000..c2131cb46
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToHtml.java.html
    @@ -0,0 +1,33 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertPDFToHtml.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ConvertPDFToHtml.java</span></div><h1>ConvertPDFToHtml.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.utils.PDFToFile;
    +
    +@RestController
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +<span class="nc" id="L19">public class ConvertPDFToHtml {</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf/html&quot;)
    +    @Operation(
    +            summary = &quot;Convert PDF to HTML&quot;,
    +            description =
    +                    &quot;This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; processPdfToHTML(@ModelAttribute PDFFile request)
    +            throws Exception {
    +<span class="nc" id="L28">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L29">        PDFToFile pdfToFile = new PDFToFile();</span>
    +<span class="nc" id="L30">        return pdfToFile.processPdfToHtml(inputFile);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToOffice.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToOffice.html
    new file mode 100644
    index 000000000..fee1cb288
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToOffice.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertPDFToOffice</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ConvertPDFToOffice</span></div><h1>ConvertPDFToOffice</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">99 of 99</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">26</td><td class="ctr2">26</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a2"><a href="ConvertPDFToOffice.java.html#L64" class="el_method">processPdfToRTForTXT(PdfToTextOrRTFRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="48" alt="48"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ConvertPDFToOffice.java.html#L49" class="el_method">processPdfToPresentation(PdfToPresentationRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="16" alt="16"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="ConvertPDFToOffice.java.html#L91" class="el_method">processPdfToWord(PdfToWordRequest)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="16" alt="16"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="ConvertPDFToOffice.java.html#L105" class="el_method">processPdfToXML(PDFFile)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="13" alt="13"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="ConvertPDFToOffice.java.html#L36" class="el_method">ConvertPDFToOffice(CustomPDFDocumentFactory)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">3</td><td class="ctr2" id="i4">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToOffice.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToOffice.java.html
    new file mode 100644
    index 000000000..1ad2e7093
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToOffice.java.html
    @@ -0,0 +1,111 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertPDFToOffice.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ConvertPDFToOffice.java</span></div><h1>ConvertPDFToOffice.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.text.PDFTextStripper;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.model.api.converters.PdfToPresentationRequest;
    +import stirling.software.SPDF.model.api.converters.PdfToTextOrRTFRequest;
    +import stirling.software.SPDF.model.api.converters.PdfToWordRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.PDFToFile;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +public class ConvertPDFToOffice {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L36">    public ConvertPDFToOffice(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L37">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L38">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf/presentation&quot;)
    +    @Operation(
    +            summary = &quot;Convert PDF to Presentation format&quot;,
    +            description =
    +                    &quot;This endpoint converts a given PDF file to a Presentation format. Input:PDF&quot;
    +                            + &quot; Output:PPT Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; processPdfToPresentation(
    +            @ModelAttribute PdfToPresentationRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L49">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L50">        String outputFormat = request.getOutputFormat();</span>
    +<span class="nc" id="L51">        PDFToFile pdfToFile = new PDFToFile();</span>
    +<span class="nc" id="L52">        return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, &quot;impress_pdf_import&quot;);</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf/text&quot;)
    +    @Operation(
    +            summary = &quot;Convert PDF to Text or RTF format&quot;,
    +            description =
    +                    &quot;This endpoint converts a given PDF file to Text or RTF format. Input:PDF&quot;
    +                            + &quot; Output:TXT Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; processPdfToRTForTXT(
    +            @ModelAttribute PdfToTextOrRTFRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L64">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L65">        String outputFormat = request.getOutputFormat();</span>
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">        if (&quot;txt&quot;.equals(request.getOutputFormat())) {</span>
    +<span class="nc" id="L67">            try (PDDocument document = pdfDocumentFactory.load(inputFile)) {</span>
    +<span class="nc" id="L68">                PDFTextStripper stripper = new PDFTextStripper();</span>
    +<span class="nc" id="L69">                String text = stripper.getText(document);</span>
    +<span class="nc" id="L70">                return WebResponseUtils.bytesToWebResponse(</span>
    +<span class="nc" id="L71">                        text.getBytes(),</span>
    +<span class="nc" id="L72">                        Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +<span class="nc" id="L73">                                        .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                                + &quot;.txt&quot;,
    +                        MediaType.TEXT_PLAIN);
    +            }
    +        } else {
    +<span class="nc" id="L78">            PDFToFile pdfToFile = new PDFToFile();</span>
    +<span class="nc" id="L79">            return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, &quot;writer_pdf_import&quot;);</span>
    +        }
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf/word&quot;)
    +    @Operation(
    +            summary = &quot;Convert PDF to Word document&quot;,
    +            description =
    +                    &quot;This endpoint converts a given PDF file to a Word document format. Input:PDF&quot;
    +                            + &quot; Output:WORD Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; processPdfToWord(@ModelAttribute PdfToWordRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L91">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L92">        String outputFormat = request.getOutputFormat();</span>
    +<span class="nc" id="L93">        PDFToFile pdfToFile = new PDFToFile();</span>
    +<span class="nc" id="L94">        return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, &quot;writer_pdf_import&quot;);</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf/xml&quot;)
    +    @Operation(
    +            summary = &quot;Convert PDF to XML&quot;,
    +            description =
    +                    &quot;This endpoint converts a PDF file to an XML file. Input:PDF Output:XML&quot;
    +                            + &quot; Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; processPdfToXML(@ModelAttribute PDFFile request)
    +            throws Exception {
    +<span class="nc" id="L105">        MultipartFile inputFile = request.getFileInput();</span>
    +
    +<span class="nc" id="L107">        PDFToFile pdfToFile = new PDFToFile();</span>
    +<span class="nc" id="L108">        return pdfToFile.processPdfToOfficeFormat(inputFile, &quot;xml&quot;, &quot;writer_pdf_import&quot;);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToPDFA.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToPDFA.html
    new file mode 100644
    index 000000000..fa9990e5c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToPDFA.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertPDFToPDFA</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ConvertPDFToPDFA</span></div><h1>ConvertPDFToPDFA</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">187 of 187</td><td class="ctr2">0%</td><td class="bar">22 of 22</td><td class="ctr2">0%</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">42</td><td class="ctr2">42</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="ConvertPDFToPDFA.java.html#L43" class="el_method">pdfToPdfA(PdfToPdfARequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="180" alt="180"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">40</td><td class="ctr2" id="i0">40</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="ConvertPDFToPDFA.java.html#L32" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="ConvertPDFToPDFA.java.html#L34" class="el_method">ConvertPDFToPDFA()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToPDFA.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToPDFA.java.html
    new file mode 100644
    index 000000000..eab566f9c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertPDFToPDFA.java.html
    @@ -0,0 +1,127 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertPDFToPDFA.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ConvertPDFToPDFA.java</span></div><h1>ConvertPDFToPDFA.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import java.io.File;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
    +
    +import org.apache.commons.io.FileUtils;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
    +import stirling.software.SPDF.utils.ProcessExecutor;
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +<span class="nc" id="L32">@Slf4j</span>
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +<span class="nc" id="L34">public class ConvertPDFToPDFA {</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf/pdfa&quot;)
    +    @Operation(
    +            summary = &quot;Convert a PDF to a PDF/A&quot;,
    +            description =
    +                    &quot;This endpoint converts a PDF file to a PDF/A file using LibreOffice. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; pdfToPdfA(@ModelAttribute PdfToPdfARequest request)
    +            throws Exception {
    +<span class="nc" id="L43">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L44">        String outputFormat = request.getOutputFormat();</span>
    +
    +        // Validate input file type
    +<span class="nc bnc" id="L47" title="All 2 branches missed.">        if (!&quot;application/pdf&quot;.equals(inputFile.getContentType())) {</span>
    +<span class="nc" id="L48">            log.error(&quot;Invalid input file type: {}&quot;, inputFile.getContentType());</span>
    +<span class="nc" id="L49">            throw new IllegalArgumentException(&quot;Input file must be a PDF&quot;);</span>
    +        }
    +
    +        // Get the original filename without extension
    +<span class="nc" id="L53">        String originalFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());</span>
    +<span class="nc bnc" id="L54" title="All 4 branches missed.">        if (originalFileName == null || originalFileName.trim().isEmpty()) {</span>
    +<span class="nc" id="L55">            originalFileName = &quot;output.pdf&quot;;</span>
    +        }
    +        String baseFileName =
    +<span class="nc bnc" id="L58" title="All 2 branches missed.">                originalFileName.contains(&quot;.&quot;)</span>
    +<span class="nc" id="L59">                        ? originalFileName.substring(0, originalFileName.lastIndexOf('.'))</span>
    +<span class="nc" id="L60">                        : originalFileName;</span>
    +
    +<span class="nc" id="L62">        Path tempInputFile = null;</span>
    +<span class="nc" id="L63">        Path tempOutputDir = null;</span>
    +        byte[] fileBytes;
    +
    +        try {
    +            // Save uploaded file to temp location
    +<span class="nc" id="L68">            tempInputFile = Files.createTempFile(&quot;input_&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L69">            inputFile.transferTo(tempInputFile);</span>
    +
    +            // Create temp output directory
    +<span class="nc" id="L72">            tempOutputDir = Files.createTempDirectory(&quot;output_&quot;);</span>
    +
    +            // Determine PDF/A filter based on requested format
    +            String pdfFilter =
    +<span class="nc bnc" id="L76" title="All 2 branches missed.">                    &quot;pdfa&quot;.equals(outputFormat)</span>
    +<span class="nc" id="L77">                            ? &quot;pdf:writer_pdf_Export:{\&quot;SelectPdfVersion\&quot;:{\&quot;type\&quot;:\&quot;long\&quot;,\&quot;value\&quot;:\&quot;2\&quot;}}&quot;</span>
    +<span class="nc" id="L78">                            : &quot;pdf:writer_pdf_Export:{\&quot;SelectPdfVersion\&quot;:{\&quot;type\&quot;:\&quot;long\&quot;,\&quot;value\&quot;:\&quot;1\&quot;}}&quot;;</span>
    +
    +            // Prepare LibreOffice command
    +<span class="nc" id="L81">            List&lt;String&gt; command =</span>
    +                    new ArrayList&lt;&gt;(
    +<span class="nc" id="L83">                            Arrays.asList(</span>
    +                                    &quot;soffice&quot;,
    +                                    &quot;--headless&quot;,
    +                                    &quot;--nologo&quot;,
    +                                    &quot;--convert-to&quot;,
    +                                    pdfFilter,
    +                                    &quot;--outdir&quot;,
    +<span class="nc" id="L90">                                    tempOutputDir.toString(),</span>
    +<span class="nc" id="L91">                                    tempInputFile.toString()));</span>
    +
    +<span class="nc" id="L93">            ProcessExecutorResult returnCode =</span>
    +<span class="nc" id="L94">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)</span>
    +<span class="nc" id="L95">                            .runCommandWithOutputHandling(command);</span>
    +
    +<span class="nc bnc" id="L97" title="All 2 branches missed.">            if (returnCode.getRc() != 0) {</span>
    +<span class="nc" id="L98">                log.error(&quot;PDF/A conversion failed with return code: {}&quot;, returnCode.getRc());</span>
    +<span class="nc" id="L99">                throw new RuntimeException(&quot;PDF/A conversion failed&quot;);</span>
    +            }
    +
    +            // Get the output file
    +<span class="nc" id="L103">            File[] outputFiles = tempOutputDir.toFile().listFiles();</span>
    +<span class="nc bnc" id="L104" title="All 4 branches missed.">            if (outputFiles == null || outputFiles.length != 1) {</span>
    +<span class="nc" id="L105">                throw new RuntimeException(</span>
    +                        &quot;Expected exactly one output file but found &quot;
    +<span class="nc bnc" id="L107" title="All 2 branches missed.">                                + (outputFiles == null ? &quot;none&quot; : outputFiles.length));</span>
    +            }
    +
    +<span class="nc" id="L110">            fileBytes = FileUtils.readFileToByteArray(outputFiles[0]);</span>
    +<span class="nc" id="L111">            String outputFilename = baseFileName + &quot;_PDFA.pdf&quot;;</span>
    +
    +<span class="nc" id="L113">            return WebResponseUtils.bytesToWebResponse(</span>
    +                    fileBytes, outputFilename, MediaType.APPLICATION_PDF);
    +
    +        } finally {
    +            // Clean up temporary files
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">            if (tempInputFile != null) {</span>
    +<span class="nc" id="L119">                Files.deleteIfExists(tempInputFile);</span>
    +            }
    +<span class="nc bnc" id="L121" title="All 2 branches missed.">            if (tempOutputDir != null) {</span>
    +<span class="nc" id="L122">                FileUtils.deleteDirectory(tempOutputDir.toFile());</span>
    +            }
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertWebsiteToPDF.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertWebsiteToPDF.html
    new file mode 100644
    index 000000000..614309b0c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertWebsiteToPDF.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertWebsiteToPDF</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ConvertWebsiteToPDF</span></div><h1>ConvertWebsiteToPDF</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">132 of 132</td><td class="ctr2">0%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">36</td><td class="ctr2">36</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a3"><a href="ConvertWebsiteToPDF.java.html#L59" class="el_method">urlToPdf(UrlToPdfRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="99" alt="99"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">26</td><td class="ctr2" id="i0">26</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ConvertWebsiteToPDF.java.html#L111" class="el_method">convertURLToFileName(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="17" alt="17"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ConvertWebsiteToPDF.java.html#L45" class="el_method">ConvertWebsiteToPDF(CustomPDFDocumentFactory, RuntimePathConfig, ApplicationProperties)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="12" alt="12"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="ConvertWebsiteToPDF.java.html#L33" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertWebsiteToPDF.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertWebsiteToPDF.java.html
    new file mode 100644
    index 000000000..45c5db79f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ConvertWebsiteToPDF.java.html
    @@ -0,0 +1,118 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertWebsiteToPDF.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ConvertWebsiteToPDF.java</span></div><h1>ConvertWebsiteToPDF.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.RuntimePathConfig;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.api.converters.UrlToPdfRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +import stirling.software.SPDF.utils.ProcessExecutor;
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +<span class="nc" id="L33">@Slf4j</span>
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +public class ConvertWebsiteToPDF {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +    private final RuntimePathConfig runtimePathConfig;
    +    private final ApplicationProperties applicationProperties;
    +
    +    @Autowired
    +    public ConvertWebsiteToPDF(
    +            CustomPDFDocumentFactory pdfDocumentFactory,
    +            RuntimePathConfig runtimePathConfig,
    +<span class="nc" id="L45">            ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L46">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L47">        this.runtimePathConfig = runtimePathConfig;</span>
    +<span class="nc" id="L48">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L49">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/url/pdf&quot;)
    +    @Operation(
    +            summary = &quot;Convert a URL to a PDF&quot;,
    +            description =
    +                    &quot;This endpoint fetches content from a URL and converts it to a PDF format.&quot;
    +                            + &quot; Input:N/A Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; urlToPdf(@ModelAttribute UrlToPdfRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L59">        String URL = request.getUrlInput();</span>
    +
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        if (!applicationProperties.getSystem().getEnableUrlToPDF()) {</span>
    +<span class="nc" id="L62">            throw new IllegalArgumentException(&quot;This endpoint has been disabled by the admin.&quot;);</span>
    +        }
    +        // Validate the URL format
    +<span class="nc bnc" id="L65" title="All 4 branches missed.">        if (!URL.matches(&quot;^https?://.*&quot;) || !GeneralUtils.isValidURL(URL)) {</span>
    +<span class="nc" id="L66">            throw new IllegalArgumentException(&quot;Invalid URL format provided.&quot;);</span>
    +        }
    +
    +        // validate the URL is reachable
    +<span class="nc bnc" id="L70" title="All 2 branches missed.">        if (!GeneralUtils.isURLReachable(URL)) {</span>
    +<span class="nc" id="L71">            throw new IllegalArgumentException(&quot;URL is not reachable, please provide a valid URL.&quot;);</span>
    +        }
    +
    +<span class="nc" id="L74">        Path tempOutputFile = null;</span>
    +<span class="nc" id="L75">        PDDocument doc = null;</span>
    +        try {
    +            // Prepare the output file path
    +<span class="nc" id="L78">            tempOutputFile = Files.createTempFile(&quot;output_&quot;, &quot;.pdf&quot;);</span>
    +
    +            // Prepare the WeasyPrint command
    +<span class="nc" id="L81">            List&lt;String&gt; command = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L82">            command.add(runtimePathConfig.getWeasyPrintPath());</span>
    +<span class="nc" id="L83">            command.add(URL);</span>
    +<span class="nc" id="L84">            command.add(&quot;--pdf-forms&quot;);</span>
    +<span class="nc" id="L85">            command.add(tempOutputFile.toString());</span>
    +
    +<span class="nc" id="L87">            ProcessExecutorResult returnCode =</span>
    +<span class="nc" id="L88">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)</span>
    +<span class="nc" id="L89">                            .runCommandWithOutputHandling(command);</span>
    +
    +            // Load the PDF using pdfDocumentFactory
    +<span class="nc" id="L92">            doc = pdfDocumentFactory.load(tempOutputFile.toFile());</span>
    +
    +            // Convert URL to a safe filename
    +<span class="nc" id="L95">            String outputFilename = convertURLToFileName(URL);</span>
    +
    +<span class="nc" id="L97">            return WebResponseUtils.pdfDocToWebResponse(doc, outputFilename);</span>
    +        } finally {
    +
    +<span class="nc bnc" id="L100" title="All 2 branches missed.">            if (tempOutputFile != null) {</span>
    +                try {
    +<span class="nc" id="L102">                    Files.deleteIfExists(tempOutputFile);</span>
    +<span class="nc" id="L103">                } catch (IOException e) {</span>
    +<span class="nc" id="L104">                    log.error(&quot;Error deleting temporary output file&quot;, e);</span>
    +<span class="nc" id="L105">                }</span>
    +            }
    +        }
    +    }
    +
    +    private String convertURLToFileName(String url) {
    +<span class="nc" id="L111">        String safeName = url.replaceAll(&quot;[^a-zA-Z0-9]&quot;, &quot;_&quot;);</span>
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">        if (safeName.length() &gt; 50) {</span>
    +<span class="nc" id="L113">            safeName = safeName.substring(0, 50); // restrict to 50 characters</span>
    +        }
    +<span class="nc" id="L115">        return safeName + &quot;.pdf&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController$CsvEntry.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController$CsvEntry.html
    new file mode 100644
    index 000000000..524a95974
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController$CsvEntry.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExtractCSVController.CsvEntry</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ExtractCSVController.CsvEntry</span></div><h1>ExtractCSVController.CsvEntry</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">9 of 9</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ExtractCSVController.java.html#L137" class="el_method">ExtractCSVController.CsvEntry(String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController.html
    new file mode 100644
    index 000000000..0378f0ccb
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExtractCSVController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">ExtractCSVController</span></div><h1>ExtractCSVController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">260 of 260</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">53</td><td class="ctr2">53</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a5"><a href="ExtractCSVController.java.html#L60" class="el_method">pdfToCsv(PDFWithPageNums)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="141" alt="141"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h0">25</td><td class="ctr2" id="i0">25</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ExtractCSVController.java.html#L98" class="el_method">createZipResponse(List, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="61" alt="61"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">15</td><td class="ctr2" id="i1">15</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="ExtractCSVController.java.html#L119" class="el_method">createCsvResponse(ExtractCSVController.CsvEntry, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="24" alt="24"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">7</td><td class="ctr2" id="i2">7</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="ExtractCSVController.java.html#L130" class="el_method">generateEntryName(String, int, int)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="19" alt="19"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="ExtractCSVController.java.html#L49" class="el_method">ExtractCSVController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a4"><a href="ExtractCSVController.java.html#L134" class="el_method">getBaseName(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="ExtractCSVController.java.html#L43" class="el_method">static {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController.java.html b/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController.java.html
    new file mode 100644
    index 000000000..0f5be2f43
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/ExtractCSVController.java.html
    @@ -0,0 +1,139 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExtractCSVController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_source">ExtractCSVController.java</span></div><h1>ExtractCSVController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.converters;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.io.StringWriter;
    +import java.nio.charset.StandardCharsets;
    +import java.util.ArrayList;
    +import java.util.Collections;
    +import java.util.List;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.commons.csv.CSVFormat;
    +import org.apache.commons.csv.QuoteMode;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ContentDisposition;
    +import org.springframework.http.HttpHeaders;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.PDFWithPageNums;
    +import stirling.software.SPDF.pdf.FlexibleCSVWriter;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +
    +import technology.tabula.ObjectExtractor;
    +import technology.tabula.Page;
    +import technology.tabula.Table;
    +import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +<span class="nc" id="L43">@Slf4j</span>
    +public class ExtractCSVController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L49">    public ExtractCSVController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L50">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L51">    }</span>
    +
    +    @PostMapping(value = &quot;/pdf/csv&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Extracts a CSV document from a PDF&quot;,
    +            description =
    +                    &quot;This operation takes an input PDF file and returns CSV file of whole page.&quot;
    +                            + &quot; Input:PDF Output:CSV Type:SISO&quot;)
    +    public ResponseEntity&lt;?&gt; pdfToCsv(@ModelAttribute PDFWithPageNums form) throws Exception {
    +<span class="nc" id="L60">        String baseName = getBaseName(form.getFileInput().getOriginalFilename());</span>
    +<span class="nc" id="L61">        List&lt;CsvEntry&gt; csvEntries = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc" id="L63">        try (PDDocument document = pdfDocumentFactory.load(form)) {</span>
    +<span class="nc" id="L64">            List&lt;Integer&gt; pages = form.getPageNumbersList(document, true);</span>
    +<span class="nc" id="L65">            SpreadsheetExtractionAlgorithm sea = new SpreadsheetExtractionAlgorithm();</span>
    +<span class="nc" id="L66">            CSVFormat format =</span>
    +<span class="nc" id="L67">                    CSVFormat.EXCEL.builder().setEscape('&quot;').setQuoteMode(QuoteMode.ALL).build();</span>
    +
    +<span class="nc bnc" id="L69" title="All 2 branches missed.">            for (int pageNum : pages) {</span>
    +<span class="nc" id="L70">                try (ObjectExtractor extractor = new ObjectExtractor(document)) {</span>
    +<span class="nc" id="L71">                    log.info(&quot;{}&quot;, pageNum);</span>
    +<span class="nc" id="L72">                    Page page = extractor.extract(pageNum);</span>
    +<span class="nc" id="L73">                    List&lt;Table&gt; tables = sea.extract(page);</span>
    +
    +<span class="nc bnc" id="L75" title="All 2 branches missed.">                    for (int i = 0; i &lt; tables.size(); i++) {</span>
    +<span class="nc" id="L76">                        StringWriter sw = new StringWriter();</span>
    +<span class="nc" id="L77">                        FlexibleCSVWriter csvWriter = new FlexibleCSVWriter(format);</span>
    +<span class="nc" id="L78">                        csvWriter.write(sw, Collections.singletonList(tables.get(i)));</span>
    +
    +<span class="nc" id="L80">                        String entryName = generateEntryName(baseName, pageNum, i + 1);</span>
    +<span class="nc" id="L81">                        csvEntries.add(new CsvEntry(entryName, sw.toString()));</span>
    +                    }
    +                }
    +<span class="nc" id="L84">            }</span>
    +
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">            if (csvEntries.isEmpty()) {</span>
    +<span class="nc" id="L87">                return ResponseEntity.noContent().build();</span>
    +<span class="nc bnc" id="L88" title="All 2 branches missed.">            } else if (csvEntries.size() == 1) {</span>
    +<span class="nc" id="L89">                return createCsvResponse(csvEntries.get(0), baseName);</span>
    +            } else {
    +<span class="nc" id="L91">                return createZipResponse(csvEntries, baseName);</span>
    +            }
    +<span class="nc bnc" id="L93" title="All 4 branches missed.">        }</span>
    +    }
    +
    +    private ResponseEntity&lt;byte[]&gt; createZipResponse(List&lt;CsvEntry&gt; entries, String baseName)
    +            throws IOException {
    +<span class="nc" id="L98">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L99">        try (ZipOutputStream zipOut = new ZipOutputStream(baos)) {</span>
    +<span class="nc bnc" id="L100" title="All 2 branches missed.">            for (CsvEntry entry : entries) {</span>
    +<span class="nc" id="L101">                ZipEntry zipEntry = new ZipEntry(entry.filename());</span>
    +<span class="nc" id="L102">                zipOut.putNextEntry(zipEntry);</span>
    +<span class="nc" id="L103">                zipOut.write(entry.content().getBytes(StandardCharsets.UTF_8));</span>
    +<span class="nc" id="L104">                zipOut.closeEntry();</span>
    +<span class="nc" id="L105">            }</span>
    +        }
    +
    +<span class="nc" id="L108">        HttpHeaders headers = new HttpHeaders();</span>
    +<span class="nc" id="L109">        headers.setContentDisposition(</span>
    +<span class="nc" id="L110">                ContentDisposition.builder(&quot;attachment&quot;)</span>
    +<span class="nc" id="L111">                        .filename(baseName + &quot;_extracted.zip&quot;)</span>
    +<span class="nc" id="L112">                        .build());</span>
    +<span class="nc" id="L113">        headers.setContentType(MediaType.parseMediaType(&quot;application/zip&quot;));</span>
    +
    +<span class="nc" id="L115">        return ResponseEntity.ok().headers(headers).body(baos.toByteArray());</span>
    +    }
    +
    +    private ResponseEntity&lt;String&gt; createCsvResponse(CsvEntry entry, String baseName) {
    +<span class="nc" id="L119">        HttpHeaders headers = new HttpHeaders();</span>
    +<span class="nc" id="L120">        headers.setContentDisposition(</span>
    +<span class="nc" id="L121">                ContentDisposition.builder(&quot;attachment&quot;)</span>
    +<span class="nc" id="L122">                        .filename(baseName + &quot;_extracted.csv&quot;)</span>
    +<span class="nc" id="L123">                        .build());</span>
    +<span class="nc" id="L124">        headers.setContentType(MediaType.parseMediaType(&quot;text/csv&quot;));</span>
    +
    +<span class="nc" id="L126">        return ResponseEntity.ok().headers(headers).body(entry.content());</span>
    +    }
    +
    +    private String generateEntryName(String baseName, int pageNum, int tableIndex) {
    +<span class="nc" id="L130">        return String.format(&quot;%s_p%d_t%d.csv&quot;, baseName, pageNum, tableIndex);</span>
    +    }
    +
    +    private String getBaseName(String filename) {
    +<span class="nc" id="L134">        return filename.replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +    }
    +
    +<span class="nc" id="L137">    private record CsvEntry(String filename, String content) {}</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/TableAttributeProvider.html b/testResult/html/stirling.software.SPDF.controller.api.converters/TableAttributeProvider.html
    new file mode 100644
    index 000000000..8ec5b7f93
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/TableAttributeProvider.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TableAttributeProvider</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.converters</a> &gt; <span class="el_class">TableAttributeProvider</span></div><h1>TableAttributeProvider</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 12</td><td class="ctr2">100%</td><td class="bar">0 of 2</td><td class="ctr2">100%</td><td class="ctr1">0</td><td class="ctr2">3</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ConvertMarkdownToPdf.java.html#L106" class="el_method">setAttributes(Node, String, Map)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">100%</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ConvertMarkdownToPdf.java.html#L103" class="el_method">TableAttributeProvider()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="40" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/index.html b/testResult/html/stirling.software.SPDF.controller.api.converters/index.html
    new file mode 100644
    index 000000000..1990ed393
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.converters</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.converters</span></div><h1>stirling.software.SPDF.controller.api.converters</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,394 of 1,506</td><td class="ctr2">7%</td><td class="bar">123 of 130</td><td class="ctr2">5%</td><td class="ctr1">97</td><td class="ctr2">105</td><td class="ctr1">347</td><td class="ctr2">380</td><td class="ctr1">35</td><td class="ctr2">40</td><td class="ctr1">9</td><td class="ctr2">11</td></tr></tfoot><tbody><tr><td id="a1"><a href="ConvertImgPDFController.html" class="el_class">ConvertImgPDFController</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="487" alt="487"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="56" alt="56"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f0">35</td><td class="ctr2" id="g0">35</td><td class="ctr1" id="h0">131</td><td class="ctr2" id="i0">131</td><td class="ctr1" id="j0">7</td><td class="ctr2" id="k0">7</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a8"><a href="ExtractCSVController.html" class="el_class">ExtractCSVController</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="260" alt="260"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="14" alt="14"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f1">14</td><td class="ctr2" id="g1">14</td><td class="ctr1" id="h1">53</td><td class="ctr2" id="i1">53</td><td class="ctr1" id="j1">7</td><td class="ctr2" id="k1">7</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a6"><a href="ConvertPDFToPDFA.html" class="el_class">ConvertPDFToPDFA</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="187" alt="187"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="47" height="10" title="22" alt="22"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f2">14</td><td class="ctr2" id="g2">14</td><td class="ctr1" id="h2">42</td><td class="ctr2" id="i2">42</td><td class="ctr1" id="j5">3</td><td class="ctr2" id="k5">3</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="ConvertOfficeController.html" class="el_class">ConvertOfficeController</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="133" alt="133"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="8" alt="8"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">8</td><td class="ctr2" id="g4">8</td><td class="ctr1" id="h4">32</td><td class="ctr2" id="i4">32</td><td class="ctr1" id="j3">4</td><td class="ctr2" id="k3">4</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a7"><a href="ConvertWebsiteToPDF.html" class="el_class">ConvertWebsiteToPDF</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="132" alt="132"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="12" alt="12"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f3">10</td><td class="ctr2" id="g3">10</td><td class="ctr1" id="h3">36</td><td class="ctr2" id="i3">36</td><td class="ctr1" id="j4">4</td><td class="ctr2" id="k4">4</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a5"><a href="ConvertPDFToOffice.html" class="el_class">ConvertPDFToOffice</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="99" alt="99"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f5">6</td><td class="ctr2" id="g5">6</td><td class="ctr1" id="h5">26</td><td class="ctr2" id="i6">26</td><td class="ctr1" id="j2">5</td><td class="ctr2" id="k2">5</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a0"><a href="ConvertHtmlToPDF.html" class="el_class">ConvertHtmlToPDF</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="73" alt="73"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="8" alt="8"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f6">6</td><td class="ctr2" id="g6">6</td><td class="ctr1" id="h6">22</td><td class="ctr2" id="i7">22</td><td class="ctr1" id="j6">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a4"><a href="ConvertPDFToHtml.html" class="el_class">ConvertPDFToHtml</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="14" alt="14"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h7">4</td><td class="ctr2" id="i8">4</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k8">2</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a9"><a href="ExtractCSVController$CsvEntry.html" class="el_class">ExtractCSVController.CsvEntry</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="9" alt="9"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k10">1</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a2"><a href="ConvertMarkdownToPdf.html" class="el_class">ConvertMarkdownToPdf</a></td><td class="bar" id="b9"><img src="../jacoco-resources/greenbar.gif" width="24" height="10" title="100" alt="100"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="1" alt="1"/><img src="../jacoco-resources/greenbar.gif" width="10" height="10" title="5" alt="5"/></td><td class="ctr2" id="e1">83%</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g7">6</td><td class="ctr1" id="h9">0</td><td class="ctr2" id="i5">29</td><td class="ctr1" id="j9">0</td><td class="ctr2" id="k6">3</td><td class="ctr1" id="l9">0</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a10"><a href="TableAttributeProvider.html" class="el_class">TableAttributeProvider</a></td><td class="bar" id="b10"><img src="../jacoco-resources/greenbar.gif" width="2" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d8"><img src="../jacoco-resources/greenbar.gif" width="4" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">100%</td><td class="ctr1" id="f10">0</td><td class="ctr2" id="g8">3</td><td class="ctr1" id="h10">0</td><td class="ctr2" id="i9">4</td><td class="ctr1" id="j10">0</td><td class="ctr2" id="k9">2</td><td class="ctr1" id="l10">0</td><td class="ctr2" id="m10">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.converters/index.source.html b/testResult/html/stirling.software.SPDF.controller.api.converters/index.source.html
    new file mode 100644
    index 000000000..0ded20949
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.converters/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.converters</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.converters</span></div><h1>stirling.software.SPDF.controller.api.converters</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,394 of 1,506</td><td class="ctr2">7%</td><td class="bar">123 of 130</td><td class="ctr2">5%</td><td class="ctr1">97</td><td class="ctr2">105</td><td class="ctr1">347</td><td class="ctr2">380</td><td class="ctr1">35</td><td class="ctr2">40</td><td class="ctr1">9</td><td class="ctr2">11</td></tr></tfoot><tbody><tr><td id="a1"><a href="ConvertImgPDFController.java.html" class="el_source">ConvertImgPDFController.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="487" alt="487"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="56" alt="56"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">35</td><td class="ctr2" id="g0">35</td><td class="ctr1" id="h0">131</td><td class="ctr2" id="i0">131</td><td class="ctr1" id="j1">7</td><td class="ctr2" id="k1">7</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a8"><a href="ExtractCSVController.java.html" class="el_source">ExtractCSVController.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="269" alt="269"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="14" alt="14"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">15</td><td class="ctr2" id="g1">15</td><td class="ctr1" id="h1">54</td><td class="ctr2" id="i1">54</td><td class="ctr1" id="j0">8</td><td class="ctr2" id="k0">8</td><td class="ctr1" id="l0">2</td><td class="ctr2" id="m0">2</td></tr><tr><td id="a6"><a href="ConvertPDFToPDFA.java.html" class="el_source">ConvertPDFToPDFA.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="187" alt="187"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="47" height="10" title="22" alt="22"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">14</td><td class="ctr2" id="g2">14</td><td class="ctr1" id="h2">42</td><td class="ctr2" id="i2">42</td><td class="ctr1" id="j5">3</td><td class="ctr2" id="k6">3</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a3"><a href="ConvertOfficeController.java.html" class="el_source">ConvertOfficeController.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="133" alt="133"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="8" alt="8"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">8</td><td class="ctr2" id="g5">8</td><td class="ctr1" id="h4">32</td><td class="ctr2" id="i5">32</td><td class="ctr1" id="j3">4</td><td class="ctr2" id="k4">4</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a7"><a href="ConvertWebsiteToPDF.java.html" class="el_source">ConvertWebsiteToPDF.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="132" alt="132"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="12" alt="12"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f3">10</td><td class="ctr2" id="g3">10</td><td class="ctr1" id="h3">36</td><td class="ctr2" id="i3">36</td><td class="ctr1" id="j4">4</td><td class="ctr2" id="k5">4</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a5"><a href="ConvertPDFToOffice.java.html" class="el_source">ConvertPDFToOffice.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="99" alt="99"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">6</td><td class="ctr2" id="g6">6</td><td class="ctr1" id="h5">26</td><td class="ctr2" id="i6">26</td><td class="ctr1" id="j2">5</td><td class="ctr2" id="k2">5</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a0"><a href="ConvertHtmlToPDF.java.html" class="el_source">ConvertHtmlToPDF.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="73" alt="73"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="8" alt="8"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f6">6</td><td class="ctr2" id="g7">6</td><td class="ctr1" id="h6">22</td><td class="ctr2" id="i7">22</td><td class="ctr1" id="j6">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a4"><a href="ConvertPDFToHtml.java.html" class="el_source">ConvertPDFToHtml.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="14" alt="14"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h7">4</td><td class="ctr2" id="i8">4</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k8">2</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a2"><a href="ConvertMarkdownToPdf.java.html" class="el_source">ConvertMarkdownToPdf.java</a></td><td class="bar" id="b8"><img src="../jacoco-resources/greenbar.gif" width="27" height="10" title="112" alt="112"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="1" alt="1"/><img src="../jacoco-resources/greenbar.gif" width="15" height="10" title="7" alt="7"/></td><td class="ctr2" id="e0">87%</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g4">9</td><td class="ctr1" id="h8">0</td><td class="ctr2" id="i4">33</td><td class="ctr1" id="j8">0</td><td class="ctr2" id="k3">5</td><td class="ctr1" id="l8">0</td><td class="ctr2" id="m1">2</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.filters/FilterController.html b/testResult/html/stirling.software.SPDF.controller.api.filters/FilterController.html
    new file mode 100644
    index 000000000..79d3cab76
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.filters/FilterController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FilterController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.filters</a> &gt; <span class="el_class">FilterController</span></div><h1>FilterController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">342 of 342</td><td class="ctr2">0%</td><td class="bar">52 of 52</td><td class="ctr2">0%</td><td class="ctr1">37</td><td class="ctr2">37</td><td class="ctr1">86</td><td class="ctr2">86</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a6"><a href="FilterController.java.html#L115" class="el_method">pageSize(PageSizeRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="86" alt="86"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h0">20</td><td class="ctr2" id="i0">20</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="FilterController.java.html#L191" class="el_method">pageRotation(PageRotationRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="94" height="10" title="68" alt="68"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">8</td><td class="ctr2" id="g1">8</td><td class="ctr1" id="h1">17</td><td class="ctr2" id="i1">17</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="FilterController.java.html#L82" class="el_method">pageCount(PDFComparisonAndCount)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="93" height="10" title="67" alt="67"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">8</td><td class="ctr2" id="g2">8</td><td class="ctr1" id="h2">16</td><td class="ctr2" id="i2">16</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="FilterController.java.html#L158" class="el_method">fileSize(FileSizeRequest)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="65" alt="65"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">8</td><td class="ctr2" id="g3">8</td><td class="ctr1" id="h3">15</td><td class="ctr2" id="i3">15</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="FilterController.java.html#L48" class="el_method">containsText(ContainsTextRequest)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="27" alt="27"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h4">8</td><td class="ctr2" id="i4">8</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="FilterController.java.html#L66" class="el_method">containsImage(PDFWithPageNums)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="23" alt="23"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h5">7</td><td class="ctr2" id="i5">7</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a3"><a href="FilterController.java.html#L38" class="el_method">FilterController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="6" alt="6"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.filters/FilterController.java.html b/testResult/html/stirling.software.SPDF.controller.api.filters/FilterController.java.html
    new file mode 100644
    index 000000000..89b986df9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.filters/FilterController.java.html
    @@ -0,0 +1,221 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FilterController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.filters</a> &gt; <span class="el_source">FilterController.java</span></div><h1>FilterController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.filters;
    +
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFComparisonAndCount;
    +import stirling.software.SPDF.model.api.PDFWithPageNums;
    +import stirling.software.SPDF.model.api.filter.ContainsTextRequest;
    +import stirling.software.SPDF.model.api.filter.FileSizeRequest;
    +import stirling.software.SPDF.model.api.filter.PageRotationRequest;
    +import stirling.software.SPDF.model.api.filter.PageSizeRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.PdfUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/filter&quot;)
    +@Tag(name = &quot;Filter&quot;, description = &quot;Filter APIs&quot;)
    +public class FilterController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L38">    public FilterController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L39">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L40">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/filter-contains-text&quot;)
    +    @Operation(
    +            summary = &quot;Checks if a PDF contains set text, returns true if does&quot;,
    +            description = &quot;Input:PDF Output:Boolean Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; containsText(@ModelAttribute ContainsTextRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L48">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L49">        String text = request.getText();</span>
    +<span class="nc" id="L50">        String pageNumber = request.getPageNumbers();</span>
    +
    +<span class="nc" id="L52">        PDDocument pdfDocument = pdfDocumentFactory.load(inputFile);</span>
    +<span class="nc bnc" id="L53" title="All 2 branches missed.">        if (PdfUtils.hasText(pdfDocument, pageNumber, text))</span>
    +<span class="nc" id="L54">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +<span class="nc" id="L55">                    pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));</span>
    +<span class="nc" id="L56">        return null;</span>
    +    }
    +
    +    // TODO
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/filter-contains-image&quot;)
    +    @Operation(
    +            summary = &quot;Checks if a PDF contains an image&quot;,
    +            description = &quot;Input:PDF Output:Boolean Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; containsImage(@ModelAttribute PDFWithPageNums request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L66">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L67">        String pageNumber = request.getPageNumbers();</span>
    +
    +<span class="nc" id="L69">        PDDocument pdfDocument = pdfDocumentFactory.load(inputFile);</span>
    +<span class="nc bnc" id="L70" title="All 2 branches missed.">        if (PdfUtils.hasImages(pdfDocument, pageNumber))</span>
    +<span class="nc" id="L71">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +<span class="nc" id="L72">                    pdfDocument, Filenames.toSimpleFileName(inputFile.getOriginalFilename()));</span>
    +<span class="nc" id="L73">        return null;</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/filter-page-count&quot;)
    +    @Operation(
    +            summary = &quot;Checks if a PDF is greater, less or equal to a setPageCount&quot;,
    +            description = &quot;Input:PDF Output:Boolean Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; pageCount(@ModelAttribute PDFComparisonAndCount request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L82">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L83">        String pageCount = request.getPageCount();</span>
    +<span class="nc" id="L84">        String comparator = request.getComparator();</span>
    +        // Load the PDF
    +<span class="nc" id="L86">        PDDocument document = pdfDocumentFactory.load(inputFile);</span>
    +<span class="nc" id="L87">        int actualPageCount = document.getNumberOfPages();</span>
    +
    +<span class="nc" id="L89">        boolean valid = false;</span>
    +        // Perform the comparison
    +<span class="nc bnc" id="L91" title="All 4 branches missed.">        switch (comparator) {</span>
    +            case &quot;Greater&quot;:
    +<span class="nc bnc" id="L93" title="All 2 branches missed.">                valid = actualPageCount &gt; Integer.parseInt(pageCount);</span>
    +<span class="nc" id="L94">                break;</span>
    +            case &quot;Equal&quot;:
    +<span class="nc bnc" id="L96" title="All 2 branches missed.">                valid = actualPageCount == Integer.parseInt(pageCount);</span>
    +<span class="nc" id="L97">                break;</span>
    +            case &quot;Less&quot;:
    +<span class="nc bnc" id="L99" title="All 2 branches missed.">                valid = actualPageCount &lt; Integer.parseInt(pageCount);</span>
    +<span class="nc" id="L100">                break;</span>
    +            default:
    +<span class="nc" id="L102">                throw new IllegalArgumentException(&quot;Invalid comparator: &quot; + comparator);</span>
    +        }
    +
    +<span class="nc bnc" id="L105" title="All 2 branches missed.">        if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);</span>
    +<span class="nc" id="L106">        return null;</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/filter-page-size&quot;)
    +    @Operation(
    +            summary = &quot;Checks if a PDF is of a certain size&quot;,
    +            description = &quot;Input:PDF Output:Boolean Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; pageSize(@ModelAttribute PageSizeRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L115">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L116">        String standardPageSize = request.getStandardPageSize();</span>
    +<span class="nc" id="L117">        String comparator = request.getComparator();</span>
    +
    +        // Load the PDF
    +<span class="nc" id="L120">        PDDocument document = pdfDocumentFactory.load(inputFile);</span>
    +
    +<span class="nc" id="L122">        PDPage firstPage = document.getPage(0);</span>
    +<span class="nc" id="L123">        PDRectangle actualPageSize = firstPage.getMediaBox();</span>
    +
    +        // Calculate the area of the actual page size
    +<span class="nc" id="L126">        float actualArea = actualPageSize.getWidth() * actualPageSize.getHeight();</span>
    +
    +        // Get the standard size and calculate its area
    +<span class="nc" id="L129">        PDRectangle standardSize = PdfUtils.textToPageSize(standardPageSize);</span>
    +<span class="nc" id="L130">        float standardArea = standardSize.getWidth() * standardSize.getHeight();</span>
    +
    +<span class="nc" id="L132">        boolean valid = false;</span>
    +        // Perform the comparison
    +<span class="nc bnc" id="L134" title="All 4 branches missed.">        switch (comparator) {</span>
    +            case &quot;Greater&quot;:
    +<span class="nc bnc" id="L136" title="All 2 branches missed.">                valid = actualArea &gt; standardArea;</span>
    +<span class="nc" id="L137">                break;</span>
    +            case &quot;Equal&quot;:
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">                valid = actualArea == standardArea;</span>
    +<span class="nc" id="L140">                break;</span>
    +            case &quot;Less&quot;:
    +<span class="nc bnc" id="L142" title="All 2 branches missed.">                valid = actualArea &lt; standardArea;</span>
    +<span class="nc" id="L143">                break;</span>
    +            default:
    +<span class="nc" id="L145">                throw new IllegalArgumentException(&quot;Invalid comparator: &quot; + comparator);</span>
    +        }
    +
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">        if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);</span>
    +<span class="nc" id="L149">        return null;</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/filter-file-size&quot;)
    +    @Operation(
    +            summary = &quot;Checks if a PDF is a set file size&quot;,
    +            description = &quot;Input:PDF Output:Boolean Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; fileSize(@ModelAttribute FileSizeRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L158">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L159">        String fileSize = request.getFileSize();</span>
    +<span class="nc" id="L160">        String comparator = request.getComparator();</span>
    +
    +        // Get the file size
    +<span class="nc" id="L163">        long actualFileSize = inputFile.getSize();</span>
    +
    +<span class="nc" id="L165">        boolean valid = false;</span>
    +        // Perform the comparison
    +<span class="nc bnc" id="L167" title="All 4 branches missed.">        switch (comparator) {</span>
    +            case &quot;Greater&quot;:
    +<span class="nc bnc" id="L169" title="All 2 branches missed.">                valid = actualFileSize &gt; Long.parseLong(fileSize);</span>
    +<span class="nc" id="L170">                break;</span>
    +            case &quot;Equal&quot;:
    +<span class="nc bnc" id="L172" title="All 2 branches missed.">                valid = actualFileSize == Long.parseLong(fileSize);</span>
    +<span class="nc" id="L173">                break;</span>
    +            case &quot;Less&quot;:
    +<span class="nc bnc" id="L175" title="All 2 branches missed.">                valid = actualFileSize &lt; Long.parseLong(fileSize);</span>
    +<span class="nc" id="L176">                break;</span>
    +            default:
    +<span class="nc" id="L178">                throw new IllegalArgumentException(&quot;Invalid comparator: &quot; + comparator);</span>
    +        }
    +
    +<span class="nc bnc" id="L181" title="All 2 branches missed.">        if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);</span>
    +<span class="nc" id="L182">        return null;</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/filter-page-rotation&quot;)
    +    @Operation(
    +            summary = &quot;Checks if a PDF is of a certain rotation&quot;,
    +            description = &quot;Input:PDF Output:Boolean Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; pageRotation(@ModelAttribute PageRotationRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L191">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L192">        int rotation = request.getRotation();</span>
    +<span class="nc" id="L193">        String comparator = request.getComparator();</span>
    +
    +        // Load the PDF
    +<span class="nc" id="L196">        PDDocument document = pdfDocumentFactory.load(inputFile);</span>
    +
    +        // Get the rotation of the first page
    +<span class="nc" id="L199">        PDPage firstPage = document.getPage(0);</span>
    +<span class="nc" id="L200">        int actualRotation = firstPage.getRotation();</span>
    +<span class="nc" id="L201">        boolean valid = false;</span>
    +        // Perform the comparison
    +<span class="nc bnc" id="L203" title="All 4 branches missed.">        switch (comparator) {</span>
    +            case &quot;Greater&quot;:
    +<span class="nc bnc" id="L205" title="All 2 branches missed.">                valid = actualRotation &gt; rotation;</span>
    +<span class="nc" id="L206">                break;</span>
    +            case &quot;Equal&quot;:
    +<span class="nc bnc" id="L208" title="All 2 branches missed.">                valid = actualRotation == rotation;</span>
    +<span class="nc" id="L209">                break;</span>
    +            case &quot;Less&quot;:
    +<span class="nc bnc" id="L211" title="All 2 branches missed.">                valid = actualRotation &lt; rotation;</span>
    +<span class="nc" id="L212">                break;</span>
    +            default:
    +<span class="nc" id="L214">                throw new IllegalArgumentException(&quot;Invalid comparator: &quot; + comparator);</span>
    +        }
    +
    +<span class="nc bnc" id="L217" title="All 2 branches missed.">        if (valid) return WebResponseUtils.multiPartFileToWebResponse(inputFile);</span>
    +<span class="nc" id="L218">        return null;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.filters/index.html b/testResult/html/stirling.software.SPDF.controller.api.filters/index.html
    new file mode 100644
    index 000000000..d2d6a1a80
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.filters/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.filters</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.filters</span></div><h1>stirling.software.SPDF.controller.api.filters</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">342 of 342</td><td class="ctr2">0%</td><td class="bar">52 of 52</td><td class="ctr2">0%</td><td class="ctr1">37</td><td class="ctr2">37</td><td class="ctr1">86</td><td class="ctr2">86</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="FilterController.html" class="el_class">FilterController</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="342" alt="342"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="52" alt="52"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">37</td><td class="ctr2" id="g0">37</td><td class="ctr1" id="h0">86</td><td class="ctr2" id="i0">86</td><td class="ctr1" id="j0">7</td><td class="ctr2" id="k0">7</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.filters/index.source.html b/testResult/html/stirling.software.SPDF.controller.api.filters/index.source.html
    new file mode 100644
    index 000000000..05c5298d4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.filters/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.filters</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.filters</span></div><h1>stirling.software.SPDF.controller.api.filters</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">342 of 342</td><td class="ctr2">0%</td><td class="bar">52 of 52</td><td class="ctr2">0%</td><td class="ctr1">37</td><td class="ctr2">37</td><td class="ctr1">86</td><td class="ctr2">86</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="FilterController.java.html" class="el_source">FilterController.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="342" alt="342"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="52" alt="52"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">37</td><td class="ctr2" id="g0">37</td><td class="ctr1" id="h0">86</td><td class="ctr2" id="i0">86</td><td class="ctr1" id="j0">7</td><td class="ctr2" id="k0">7</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController$1$LineInfo.html b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController$1$LineInfo.html
    new file mode 100644
    index 000000000..c4aedae76
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController$1$LineInfo.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AutoRenameController.1.LineInfo</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">AutoRenameController.1.LineInfo</span></div><h1>AutoRenameController.1.LineInfo</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AutoRenameController.java.html#L130" class="el_method">AutoRenameController.1.LineInfo(AutoRenameController.1, String, float)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController$1.html b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController$1.html
    new file mode 100644
    index 000000000..bb51c28cc
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AutoRenameController.new PDFTextStripper() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">AutoRenameController.new PDFTextStripper() {...}</span></div><h1>AutoRenameController.new PDFTextStripper() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">239 of 239</td><td class="ctr2">0%</td><td class="bar">26 of 26</td><td class="ctr2">0%</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">47</td><td class="ctr2">47</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="AutoRenameController.java.html#L89" class="el_method">getText(PDDocument)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="130" alt="130"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h0">27</td><td class="ctr2" id="i0">27</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="AutoRenameController.java.html#L67" class="el_method">processTextPosition(TextPosition)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="55" alt="55"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="8" alt="8"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h1">11</td><td class="ctr2" id="i1">11</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="AutoRenameController.java.html#L58" class="el_method">{...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="28" alt="28"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="AutoRenameController.java.html#L82" class="el_method">processLine()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="22" alt="22"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="AutoRenameController.java.html#L112" class="el_method">lambda$getText$0(AutoRenameController.1.LineInfo)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController.html
    new file mode 100644
    index 000000000..bbb9d3266
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AutoRenameController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">AutoRenameController</span></div><h1>AutoRenameController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">58 of 58</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="AutoRenameController.java.html#L53" class="el_method">extractHeader(ExtractHeaderRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="48" alt="48"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">11</td><td class="ctr2" id="i0">11</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="AutoRenameController.java.html#L41" class="el_method">AutoRenameController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="AutoRenameController.java.html#L31" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController.java.html
    new file mode 100644
    index 000000000..fae75be3a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoRenameController.java.html
    @@ -0,0 +1,150 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AutoRenameController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">AutoRenameController.java</span></div><h1>AutoRenameController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.Comparator;
    +import java.util.List;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.text.PDFTextStripper;
    +import org.apache.pdfbox.text.TextPosition;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.ExtractHeaderRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L31">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class AutoRenameController {
    +
    +    private static final float TITLE_FONT_SIZE_THRESHOLD = 20.0f;
    +    private static final int LINE_LIMIT = 200;
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L41">    public AutoRenameController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L42">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L43">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/auto-rename&quot;)
    +    @Operation(
    +            summary = &quot;Extract header from PDF file&quot;,
    +            description =
    +                    &quot;This endpoint accepts a PDF file and attempts to extract its title or header&quot;
    +                            + &quot; based on heuristics. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; extractHeader(@ModelAttribute ExtractHeaderRequest request)
    +            throws Exception {
    +<span class="nc" id="L53">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L54">        Boolean useFirstTextAsFallback = request.isUseFirstTextAsFallback();</span>
    +
    +<span class="nc" id="L56">        PDDocument document = pdfDocumentFactory.load(file);</span>
    +<span class="nc" id="L57">        PDFTextStripper reader =</span>
    +<span class="nc" id="L58">                new PDFTextStripper() {</span>
    +<span class="nc" id="L59">                    List&lt;LineInfo&gt; lineInfos = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L60">                    StringBuilder lineBuilder = new StringBuilder();</span>
    +<span class="nc" id="L61">                    float lastY = -1;</span>
    +<span class="nc" id="L62">                    float maxFontSizeInLine = 0.0f;</span>
    +<span class="nc" id="L63">                    int lineCount = 0;</span>
    +
    +                    @Override
    +                    protected void processTextPosition(TextPosition text) {
    +<span class="nc bnc" id="L67" title="All 4 branches missed.">                        if (lastY != text.getY() &amp;&amp; lineCount &lt; LINE_LIMIT) {</span>
    +<span class="nc" id="L68">                            processLine();</span>
    +<span class="nc" id="L69">                            lineBuilder = new StringBuilder(text.getUnicode());</span>
    +<span class="nc" id="L70">                            maxFontSizeInLine = text.getFontSizeInPt();</span>
    +<span class="nc" id="L71">                            lastY = text.getY();</span>
    +<span class="nc" id="L72">                            lineCount++;</span>
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">                        } else if (lineCount &lt; LINE_LIMIT) {</span>
    +<span class="nc" id="L74">                            lineBuilder.append(text.getUnicode());</span>
    +<span class="nc bnc" id="L75" title="All 2 branches missed.">                            if (text.getFontSizeInPt() &gt; maxFontSizeInLine) {</span>
    +<span class="nc" id="L76">                                maxFontSizeInLine = text.getFontSizeInPt();</span>
    +                            }
    +                        }
    +<span class="nc" id="L79">                    }</span>
    +
    +                    private void processLine() {
    +<span class="nc bnc" id="L82" title="All 4 branches missed.">                        if (lineBuilder.length() &gt; 0 &amp;&amp; lineCount &lt; LINE_LIMIT) {</span>
    +<span class="nc" id="L83">                            lineInfos.add(new LineInfo(lineBuilder.toString(), maxFontSizeInLine));</span>
    +                        }
    +<span class="nc" id="L85">                    }</span>
    +
    +                    @Override
    +                    public String getText(PDDocument doc) throws IOException {
    +<span class="nc" id="L89">                        this.lineInfos.clear();</span>
    +<span class="nc" id="L90">                        this.lineBuilder = new StringBuilder();</span>
    +<span class="nc" id="L91">                        this.lastY = -1;</span>
    +<span class="nc" id="L92">                        this.maxFontSizeInLine = 0.0f;</span>
    +<span class="nc" id="L93">                        this.lineCount = 0;</span>
    +<span class="nc" id="L94">                        super.getText(doc);</span>
    +<span class="nc" id="L95">                        processLine(); // Process the last line</span>
    +
    +                        // Merge lines with same font size
    +<span class="nc" id="L98">                        List&lt;LineInfo&gt; mergedLineInfos = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L99" title="All 2 branches missed.">                        for (int i = 0; i &lt; lineInfos.size(); i++) {</span>
    +<span class="nc" id="L100">                            String mergedText = lineInfos.get(i).text;</span>
    +<span class="nc" id="L101">                            float fontSize = lineInfos.get(i).fontSize;</span>
    +<span class="nc bnc" id="L102" title="All 2 branches missed.">                            while (i + 1 &lt; lineInfos.size()</span>
    +<span class="nc bnc" id="L103" title="All 2 branches missed.">                                    &amp;&amp; lineInfos.get(i + 1).fontSize == fontSize) {</span>
    +<span class="nc" id="L104">                                mergedText += &quot; &quot; + lineInfos.get(i + 1).text;</span>
    +<span class="nc" id="L105">                                i++;</span>
    +                            }
    +<span class="nc" id="L107">                            mergedLineInfos.add(new LineInfo(mergedText, fontSize));</span>
    +                        }
    +
    +                        // Sort lines by font size in descending order and get the first one
    +<span class="nc" id="L111">                        mergedLineInfos.sort(</span>
    +<span class="nc" id="L112">                                Comparator.comparing((LineInfo li) -&gt; li.fontSize).reversed());</span>
    +                        String title =
    +<span class="nc bnc" id="L114" title="All 2 branches missed.">                                mergedLineInfos.isEmpty() ? null : mergedLineInfos.get(0).text;</span>
    +
    +<span class="nc bnc" id="L116" title="All 2 branches missed.">                        return title != null</span>
    +<span class="nc" id="L117">                                ? title</span>
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">                                : (useFirstTextAsFallback</span>
    +<span class="nc bnc" id="L119" title="All 2 branches missed.">                                        ? (mergedLineInfos.isEmpty()</span>
    +<span class="nc" id="L120">                                                ? null</span>
    +<span class="nc" id="L121">                                                : mergedLineInfos.get(mergedLineInfos.size() - 1)</span>
    +<span class="nc" id="L122">                                                        .text)</span>
    +<span class="nc" id="L123">                                        : null);</span>
    +                    }
    +
    +                    class LineInfo {
    +                        String text;
    +                        float fontSize;
    +
    +<span class="nc" id="L130">                        LineInfo(String text, float fontSize) {</span>
    +<span class="nc" id="L131">                            this.text = text;</span>
    +<span class="nc" id="L132">                            this.fontSize = fontSize;</span>
    +<span class="nc" id="L133">                        }</span>
    +                    }
    +                };
    +
    +<span class="nc" id="L137">        String header = reader.getText(document);</span>
    +
    +        // Sanitize the header string by removing characters not allowed in a filename.
    +<span class="nc bnc" id="L140" title="All 4 branches missed.">        if (header != null &amp;&amp; header.length() &lt; 255) {</span>
    +<span class="nc" id="L141">            header = header.replaceAll(&quot;[/\\\\?%*:|\&quot;&lt;&gt;]&quot;, &quot;&quot;).trim();</span>
    +<span class="nc" id="L142">            return WebResponseUtils.pdfDocToWebResponse(document, header + &quot;.pdf&quot;);</span>
    +        } else {
    +<span class="nc" id="L144">            log.info(&quot;File has no good title to be found&quot;);</span>
    +<span class="nc" id="L145">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +<span class="nc" id="L146">                    document, Filenames.toSimpleFileName(file.getOriginalFilename()));</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/AutoSplitPdfController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoSplitPdfController.html
    new file mode 100644
    index 000000000..6f8e4e13c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoSplitPdfController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AutoSplitPdfController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">AutoSplitPdfController</span></div><h1>AutoSplitPdfController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">359 of 359</td><td class="ctr2">0%</td><td class="bar">32 of 32</td><td class="ctr2">0%</td><td class="ctr1">21</td><td class="ctr2">21</td><td class="ctr1">92</td><td class="ctr2">92</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="AutoSplitPdfController.java.html#L119" class="el_method">autoSplitPdf(AutoSplitPdfRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="228" alt="228"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">13</td><td class="ctr2" id="g0">13</td><td class="ctr1" id="h0">61</td><td class="ctr2" id="i0">61</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="AutoSplitPdfController.java.html#L64" class="el_method">decodeQRCode(BufferedImage)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="55" height="10" title="106" alt="106"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h1">25</td><td class="ctr2" id="i1">25</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="AutoSplitPdfController.java.html#L43" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="12" alt="12"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="AutoSplitPdfController.java.html#L157" class="el_method">lambda$autoSplitPdf$0(PDDocument)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="7" alt="7"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="AutoSplitPdfController.java.html#L57" class="el_method">AutoSplitPdfController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/AutoSplitPdfController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoSplitPdfController.java.html
    new file mode 100644
    index 000000000..848ec1cc7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/AutoSplitPdfController.java.html
    @@ -0,0 +1,215 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AutoSplitPdfController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">AutoSplitPdfController.java</span></div><h1>AutoSplitPdfController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.image.BufferedImage;
    +import java.awt.image.DataBufferByte;
    +import java.awt.image.DataBufferInt;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Set;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.rendering.PDFRenderer;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import com.google.zxing.*;
    +import com.google.zxing.common.HybridBinarizer;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.AutoSplitPdfRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L43">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class AutoSplitPdfController {
    +
    +<span class="nc" id="L47">    private static final Set&lt;String&gt; VALID_QR_CONTENTS =</span>
    +            new HashSet&lt;&gt;(
    +<span class="nc" id="L49">                    Set.of(</span>
    +                            &quot;https://github.com/Stirling-Tools/Stirling-PDF&quot;,
    +                            &quot;https://github.com/Frooodle/Stirling-PDF&quot;,
    +                            &quot;https://stirlingpdf.com&quot;));
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L57">    public AutoSplitPdfController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L58">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L59">    }</span>
    +
    +    private static String decodeQRCode(BufferedImage bufferedImage) {
    +        LuminanceSource source;
    +
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">        if (bufferedImage.getRaster().getDataBuffer() instanceof DataBufferByte dataBufferByte) {</span>
    +<span class="nc" id="L65">            byte[] pixels = dataBufferByte.getData();</span>
    +<span class="nc" id="L66">            source =</span>
    +                    new PlanarYUVLuminanceSource(
    +                            pixels,
    +<span class="nc" id="L69">                            bufferedImage.getWidth(),</span>
    +<span class="nc" id="L70">                            bufferedImage.getHeight(),</span>
    +                            0,
    +                            0,
    +<span class="nc" id="L73">                            bufferedImage.getWidth(),</span>
    +<span class="nc" id="L74">                            bufferedImage.getHeight(),</span>
    +                            false);
    +<span class="nc bnc" id="L76" title="All 2 branches missed.">        } else if (bufferedImage.getRaster().getDataBuffer()</span>
    +<span class="nc" id="L77">                instanceof DataBufferInt dataBufferInt) {</span>
    +<span class="nc" id="L78">            int[] pixels = dataBufferInt.getData();</span>
    +<span class="nc" id="L79">            byte[] newPixels = new byte[pixels.length];</span>
    +<span class="nc bnc" id="L80" title="All 2 branches missed.">            for (int i = 0; i &lt; pixels.length; i++) {</span>
    +<span class="nc" id="L81">                newPixels[i] = (byte) (pixels[i] &amp; 0xff);</span>
    +            }
    +<span class="nc" id="L83">            source =</span>
    +                    new PlanarYUVLuminanceSource(
    +                            newPixels,
    +<span class="nc" id="L86">                            bufferedImage.getWidth(),</span>
    +<span class="nc" id="L87">                            bufferedImage.getHeight(),</span>
    +                            0,
    +                            0,
    +<span class="nc" id="L90">                            bufferedImage.getWidth(),</span>
    +<span class="nc" id="L91">                            bufferedImage.getHeight(),</span>
    +                            false);
    +<span class="nc" id="L93">        } else {</span>
    +<span class="nc" id="L94">            throw new IllegalArgumentException(</span>
    +                    &quot;BufferedImage must have 8-bit gray scale, 24-bit RGB, 32-bit ARGB (packed&quot;
    +                            + &quot; int), byte gray, or 3-byte/4-byte RGB image data&quot;);
    +        }
    +
    +<span class="nc" id="L99">        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));</span>
    +
    +        try {
    +<span class="nc" id="L102">            Result result = new MultiFormatReader().decode(bitmap);</span>
    +<span class="nc" id="L103">            return result.getText();</span>
    +<span class="nc" id="L104">        } catch (NotFoundException e) {</span>
    +<span class="nc" id="L105">            return null; // there is no QR code in the image</span>
    +        }
    +    }
    +
    +    @PostMapping(value = &quot;/auto-split-pdf&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Auto split PDF pages into separate documents&quot;,
    +            description =
    +                    &quot;This endpoint accepts a PDF file, scans each page for a specific QR code, and&quot;
    +                            + &quot; splits the document at the QR code boundaries. The output is a zip file&quot;
    +                            + &quot; containing each separate PDF document. Input:PDF Output:ZIP-PDF&quot;
    +                            + &quot; Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; autoSplitPdf(@ModelAttribute AutoSplitPdfRequest request)
    +            throws IOException {
    +<span class="nc" id="L119">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L120">        boolean duplexMode = request.isDuplexMode();</span>
    +
    +<span class="nc" id="L122">        PDDocument document = null;</span>
    +<span class="nc" id="L123">        List&lt;PDDocument&gt; splitDocuments = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L124">        Path zipFile = null;</span>
    +<span class="nc" id="L125">        byte[] data = null;</span>
    +
    +        try {
    +<span class="nc" id="L128">            document = pdfDocumentFactory.load(file.getInputStream());</span>
    +<span class="nc" id="L129">            PDFRenderer pdfRenderer = new PDFRenderer(document);</span>
    +<span class="nc" id="L130">            pdfRenderer.setSubsamplingAllowed(true);</span>
    +
    +<span class="nc bnc" id="L132" title="All 2 branches missed.">            for (int page = 0; page &lt; document.getNumberOfPages(); ++page) {</span>
    +<span class="nc" id="L133">                BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 150);</span>
    +<span class="nc" id="L134">                String result = decodeQRCode(bim);</span>
    +
    +<span class="nc" id="L136">                boolean isValidQrCode = VALID_QR_CONTENTS.contains(result);</span>
    +<span class="nc" id="L137">                log.debug(&quot;detected qr code {}, code is vale={}&quot;, result, isValidQrCode);</span>
    +<span class="nc bnc" id="L138" title="All 4 branches missed.">                if (isValidQrCode &amp;&amp; page != 0) {</span>
    +<span class="nc" id="L139">                    splitDocuments.add(new PDDocument());</span>
    +                }
    +
    +<span class="nc bnc" id="L142" title="All 4 branches missed.">                if (!splitDocuments.isEmpty() &amp;&amp; !isValidQrCode) {</span>
    +<span class="nc" id="L143">                    splitDocuments.get(splitDocuments.size() - 1).addPage(document.getPage(page));</span>
    +<span class="nc bnc" id="L144" title="All 2 branches missed.">                } else if (page == 0) {</span>
    +<span class="nc" id="L145">                    PDDocument firstDocument = new PDDocument();</span>
    +<span class="nc" id="L146">                    firstDocument.addPage(document.getPage(page));</span>
    +<span class="nc" id="L147">                    splitDocuments.add(firstDocument);</span>
    +                }
    +
    +                // If duplexMode is true and current page is a divider, then skip next page
    +<span class="nc bnc" id="L151" title="All 4 branches missed.">                if (duplexMode &amp;&amp; isValidQrCode) {</span>
    +<span class="nc" id="L152">                    page++;</span>
    +                }
    +            }
    +
    +            // Remove split documents that have no pages
    +<span class="nc bnc" id="L157" title="All 2 branches missed.">            splitDocuments.removeIf(pdDocument -&gt; pdDocument.getNumberOfPages() == 0);</span>
    +
    +<span class="nc" id="L159">            zipFile = Files.createTempFile(&quot;split_documents&quot;, &quot;.zip&quot;);</span>
    +<span class="nc" id="L160">            String filename =</span>
    +<span class="nc" id="L161">                    Filenames.toSimpleFileName(file.getOriginalFilename())</span>
    +<span class="nc" id="L162">                            .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +
    +<span class="nc" id="L164">            try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {</span>
    +<span class="nc bnc" id="L165" title="All 2 branches missed.">                for (int i = 0; i &lt; splitDocuments.size(); i++) {</span>
    +<span class="nc" id="L166">                    String fileName = filename + &quot;_&quot; + (i + 1) + &quot;.pdf&quot;;</span>
    +<span class="nc" id="L167">                    PDDocument splitDocument = splitDocuments.get(i);</span>
    +
    +<span class="nc" id="L169">                    ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L170">                    splitDocument.save(baos);</span>
    +<span class="nc" id="L171">                    byte[] pdf = baos.toByteArray();</span>
    +
    +<span class="nc" id="L173">                    ZipEntry pdfEntry = new ZipEntry(fileName);</span>
    +<span class="nc" id="L174">                    zipOut.putNextEntry(pdfEntry);</span>
    +<span class="nc" id="L175">                    zipOut.write(pdf);</span>
    +<span class="nc" id="L176">                    zipOut.closeEntry();</span>
    +                }
    +            }
    +
    +<span class="nc" id="L180">            data = Files.readAllBytes(zipFile);</span>
    +
    +<span class="nc" id="L182">            return WebResponseUtils.bytesToWebResponse(</span>
    +                    data, filename + &quot;.zip&quot;, MediaType.APPLICATION_OCTET_STREAM);
    +<span class="nc" id="L184">        } catch (Exception e) {</span>
    +<span class="nc" id="L185">            log.error(&quot;Error in auto split&quot;, e);</span>
    +<span class="nc" id="L186">            throw e;</span>
    +        } finally {
    +            // Clean up resources
    +<span class="nc bnc" id="L189" title="All 2 branches missed.">            if (document != null) {</span>
    +                try {
    +<span class="nc" id="L191">                    document.close();</span>
    +<span class="nc" id="L192">                } catch (IOException e) {</span>
    +<span class="nc" id="L193">                    log.error(&quot;Error closing main PDDocument&quot;, e);</span>
    +<span class="nc" id="L194">                }</span>
    +            }
    +
    +<span class="nc bnc" id="L197" title="All 2 branches missed.">            for (PDDocument splitDoc : splitDocuments) {</span>
    +                try {
    +<span class="nc" id="L199">                    splitDoc.close();</span>
    +<span class="nc" id="L200">                } catch (IOException e) {</span>
    +<span class="nc" id="L201">                    log.error(&quot;Error closing split PDDocument&quot;, e);</span>
    +<span class="nc" id="L202">                }</span>
    +<span class="nc" id="L203">            }</span>
    +
    +<span class="nc bnc" id="L205" title="All 2 branches missed.">            if (zipFile != null) {</span>
    +                try {
    +<span class="nc" id="L207">                    Files.deleteIfExists(zipFile);</span>
    +<span class="nc" id="L208">                } catch (IOException e) {</span>
    +<span class="nc" id="L209">                    log.error(&quot;Error deleting temporary zip file&quot;, e);</span>
    +<span class="nc" id="L210">                }</span>
    +            }
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/BlankPageController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/BlankPageController.html
    new file mode 100644
    index 000000000..48e55e5e7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/BlankPageController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>BlankPageController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">BlankPageController</span></div><h1>BlankPageController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">316 of 316</td><td class="ctr2">0%</td><td class="bar">28 of 28</td><td class="ctr2">0%</td><td class="ctr1">19</td><td class="ctr2">19</td><td class="ctr1">75</td><td class="ctr2">75</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a3"><a href="BlankPageController.java.html#L85" class="el_method">removeBlankPages(RemoveBlankPagesRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="203" alt="203"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">49</td><td class="ctr2" id="i0">49</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="BlankPageController.java.html#L52" class="el_method">isBlankImage(BufferedImage, int, double, int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="71" alt="71"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h1">13</td><td class="ctr2" id="i1">13</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="BlankPageController.java.html#L162" class="el_method">createZipEntry(ZipOutputStream, List, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="32" alt="32"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">9</td><td class="ctr2" id="i2">9</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="BlankPageController.java.html#L46" class="el_method">BlankPageController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="BlankPageController.java.html#L39" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/BlankPageController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/BlankPageController.java.html
    new file mode 100644
    index 000000000..ddc2a6a9a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/BlankPageController.java.html
    @@ -0,0 +1,175 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>BlankPageController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">BlankPageController.java</span></div><h1>BlankPageController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.image.BufferedImage;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageTree;
    +import org.apache.pdfbox.rendering.PDFRenderer;
    +import org.apache.pdfbox.text.PDFTextStripper;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.RemoveBlankPagesRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.PdfUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L39">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class BlankPageController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L46">    public BlankPageController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L47">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L48">    }</span>
    +
    +    public static boolean isBlankImage(
    +            BufferedImage image, int threshold, double whitePercent, int blurSize) {
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">        if (image == null) {</span>
    +<span class="nc" id="L53">            log.info(&quot;Error: Image is null&quot;);</span>
    +<span class="nc" id="L54">            return false;</span>
    +        }
    +
    +        // Convert to binary image based on the threshold
    +<span class="nc" id="L58">        int whitePixels = 0;</span>
    +<span class="nc" id="L59">        int totalPixels = image.getWidth() * image.getHeight();</span>
    +
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        for (int i = 0; i &lt; image.getHeight(); i++) {</span>
    +<span class="nc bnc" id="L62" title="All 2 branches missed.">            for (int j = 0; j &lt; image.getWidth(); j++) {</span>
    +<span class="nc" id="L63">                int color = image.getRGB(j, i) &amp; 0xFF;</span>
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">                if (color &gt;= 255 - threshold) {</span>
    +<span class="nc" id="L65">                    whitePixels++;</span>
    +                }
    +            }
    +        }
    +
    +<span class="nc" id="L70">        double whitePixelPercentage = (whitePixels / (double) totalPixels) * 100;</span>
    +<span class="nc" id="L71">        log.info(String.format(&quot;Page has white pixel percent of %.2f%%&quot;, whitePixelPercentage));</span>
    +
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">        return whitePixelPercentage &gt;= whitePercent;</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/remove-blanks&quot;)
    +    @Operation(
    +            summary = &quot;Remove blank pages from a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint removes blank pages from a given PDF file. Users can specify the&quot;
    +                            + &quot; threshold and white percentage to tune the detection of blank pages.&quot;
    +                            + &quot; Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; removeBlankPages(@ModelAttribute RemoveBlankPagesRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L85">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L86">        int threshold = request.getThreshold();</span>
    +<span class="nc" id="L87">        float whitePercent = request.getWhitePercent();</span>
    +
    +<span class="nc" id="L89">        try (PDDocument document = pdfDocumentFactory.load(inputFile)) {</span>
    +<span class="nc" id="L90">            PDPageTree pages = document.getDocumentCatalog().getPages();</span>
    +<span class="nc" id="L91">            PDFTextStripper textStripper = new PDFTextStripper();</span>
    +
    +<span class="nc" id="L93">            List&lt;PDPage&gt; nonBlankPages = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L94">            List&lt;PDPage&gt; blankPages = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L95">            int pageIndex = 0;</span>
    +
    +<span class="nc" id="L97">            PDFRenderer pdfRenderer = new PDFRenderer(document);</span>
    +<span class="nc" id="L98">            pdfRenderer.setSubsamplingAllowed(true);</span>
    +<span class="nc bnc" id="L99" title="All 2 branches missed.">            for (PDPage page : pages) {</span>
    +<span class="nc" id="L100">                log.info(&quot;checking page {}&quot;, pageIndex);</span>
    +<span class="nc" id="L101">                textStripper.setStartPage(pageIndex + 1);</span>
    +<span class="nc" id="L102">                textStripper.setEndPage(pageIndex + 1);</span>
    +<span class="nc" id="L103">                String pageText = textStripper.getText(document);</span>
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">                boolean hasText = !pageText.trim().isEmpty();</span>
    +
    +<span class="nc" id="L106">                boolean blank = true;</span>
    +<span class="nc bnc" id="L107" title="All 2 branches missed.">                if (hasText) {</span>
    +<span class="nc" id="L108">                    log.info(&quot;page {} has text, not blank&quot;, pageIndex);</span>
    +<span class="nc" id="L109">                    blank = false;</span>
    +                } else {
    +<span class="nc" id="L111">                    boolean hasImages = PdfUtils.hasImagesOnPage(page);</span>
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">                    if (hasImages) {</span>
    +<span class="nc" id="L113">                        log.info(&quot;page {} has image, running blank detection&quot;, pageIndex);</span>
    +                        // Render image and save as temp file
    +<span class="nc" id="L115">                        BufferedImage image = pdfRenderer.renderImageWithDPI(pageIndex, 30);</span>
    +<span class="nc" id="L116">                        blank = isBlankImage(image, threshold, whitePercent, threshold);</span>
    +                    }
    +                }
    +
    +<span class="nc bnc" id="L120" title="All 2 branches missed.">                if (blank) {</span>
    +<span class="nc" id="L121">                    log.info(&quot;Skipping, Image was  blank for page #{}&quot;, pageIndex);</span>
    +<span class="nc" id="L122">                    blankPages.add(page);</span>
    +                } else {
    +<span class="nc" id="L124">                    log.info(&quot;page {} has image which is not blank&quot;, pageIndex);</span>
    +<span class="nc" id="L125">                    nonBlankPages.add(page);</span>
    +                }
    +
    +<span class="nc" id="L128">                pageIndex++;</span>
    +<span class="nc" id="L129">            }</span>
    +
    +<span class="nc" id="L131">            ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L132">            ZipOutputStream zos = new ZipOutputStream(baos);</span>
    +
    +<span class="nc" id="L134">            String filename =</span>
    +<span class="nc" id="L135">                    Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +<span class="nc" id="L136">                            .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +
    +<span class="nc bnc" id="L138" title="All 2 branches missed.">            if (!nonBlankPages.isEmpty()) {</span>
    +<span class="nc" id="L139">                createZipEntry(zos, nonBlankPages, filename + &quot;_nonBlankPages.pdf&quot;);</span>
    +            } else {
    +<span class="nc" id="L141">                createZipEntry(zos, blankPages, filename + &quot;_allBlankPages.pdf&quot;);</span>
    +            }
    +
    +<span class="nc bnc" id="L144" title="All 4 branches missed.">            if (!nonBlankPages.isEmpty() &amp;&amp; !blankPages.isEmpty()) {</span>
    +<span class="nc" id="L145">                createZipEntry(zos, blankPages, filename + &quot;_blankPages.pdf&quot;);</span>
    +            }
    +
    +<span class="nc" id="L148">            zos.close();</span>
    +
    +<span class="nc" id="L150">            log.info(&quot;Returning ZIP file: {}&quot;, filename + &quot;_processed.zip&quot;);</span>
    +<span class="nc" id="L151">            return WebResponseUtils.boasToWebResponse(</span>
    +                    baos, filename + &quot;_processed.zip&quot;, MediaType.APPLICATION_OCTET_STREAM);
    +
    +<span class="nc" id="L154">        } catch (IOException e) {</span>
    +<span class="nc" id="L155">            log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L156">            return new ResponseEntity&lt;&gt;(HttpStatus.INTERNAL_SERVER_ERROR);</span>
    +        }
    +    }
    +
    +    public void createZipEntry(ZipOutputStream zos, List&lt;PDPage&gt; pages, String entryName)
    +            throws IOException {
    +<span class="nc" id="L162">        try (PDDocument document = pdfDocumentFactory.createNewDocument()) {</span>
    +
    +<span class="nc bnc" id="L164" title="All 2 branches missed.">            for (PDPage page : pages) {</span>
    +<span class="nc" id="L165">                document.addPage(page);</span>
    +<span class="nc" id="L166">            }</span>
    +
    +<span class="nc" id="L168">            ZipEntry zipEntry = new ZipEntry(entryName);</span>
    +<span class="nc" id="L169">            zos.putNextEntry(zipEntry);</span>
    +<span class="nc" id="L170">            document.save(zos);</span>
    +<span class="nc" id="L171">            zos.closeEntry();</span>
    +        }
    +<span class="nc" id="L173">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController$CompressionStats.html b/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController$CompressionStats.html
    new file mode 100644
    index 000000000..6d042b483
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController$CompressionStats.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CompressController.CompressionStats</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">CompressController.CompressionStats</span></div><h1>CompressController.CompressionStats</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="CompressController.java.html#L94" class="el_method">CompressController.CompressionStats()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController.html
    new file mode 100644
    index 000000000..a6e2b3d70
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CompressController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">CompressController</span></div><h1>CompressController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,769 of 1,769</td><td class="ctr2">0%</td><td class="bar">173 of 173</td><td class="ctr2">0%</td><td class="ctr1">119</td><td class="ctr2">119</td><td class="ctr1">396</td><td class="ctr2">396</td><td class="ctr1">29</td><td class="ctr2">29</td></tr></tfoot><tbody><tr><td id="a24"><a href="CompressController.java.html#L658" class="el_method">optimizePdf(OptimizePdfRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="269" alt="269"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="44" alt="44"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">23</td><td class="ctr2" id="g0">23</td><td class="ctr1" id="h0">73</td><td class="ctr2" id="i0">73</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a25"><a href="CompressController.java.html#L480" class="el_method">processAndCompressImage(PDImageXObject, double, float, boolean)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="87" height="10" title="196" alt="196"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="26" alt="26"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">14</td><td class="ctr2" id="g1">14</td><td class="ctr1" id="h1">41</td><td class="ctr2" id="i1">41</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a10"><a href="CompressController.java.html#L280" class="el_method">createCompressedImages(PDDocument, Map, double, float, boolean, CompressController.CompressionStats)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="67" height="10" title="151" alt="151"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f7">4</td><td class="ctr2" id="g7">4</td><td class="ctr1" id="h4">26</td><td class="ctr2" id="i4">26</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="CompressController.java.html#L789" class="el_method">applyQpdfCompression(OptimizePdfRequest, int, Path, List)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="62" height="10" title="141" alt="141"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="10" alt="10"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f5">6</td><td class="ctr2" id="g5">6</td><td class="ctr1" id="h2">34</td><td class="ctr2" id="i2">34</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a7"><a href="CompressController.java.html#L107" class="el_method">compressImagesInPDF(Path, double, float, boolean)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="58" height="10" title="131" alt="131"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h3">27</td><td class="ctr2" id="i3">27</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a4"><a href="CompressController.java.html#L220" class="el_method">checkFormForImages(int, COSName, PDFormXObject, Map)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="101" alt="101"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="8" alt="8"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f6">5</td><td class="ctr2" id="g6">5</td><td class="ctr1" id="h5">24</td><td class="ctr2" id="i5">24</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a12"><a href="CompressController.java.html#L157" class="el_method">findImages(PDDocument)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="98" alt="98"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="12" alt="12"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f3">7</td><td class="ctr2" id="g3">7</td><td class="ctr1" id="h6">18</td><td class="ctr2" id="i6">18</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a23"><a href="CompressController.java.html#L444" class="el_method">logCompressionStats(CompressController.CompressionStats, long)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="39" height="10" title="89" alt="89"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f15">2</td><td class="ctr2" id="g15">2</td><td class="ctr1" id="h9">15</td><td class="ctr2" id="i9">15</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a26"><a href="CompressController.java.html#L412" class="el_method">replaceImageReference(PDDocument, CompressController.ImageReference, PDImageXObject)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="76" alt="76"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f16">2</td><td class="ctr2" id="g16">2</td><td class="ctr1" id="h7">17</td><td class="ctr2" id="i7">17</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a8"><a href="CompressController.java.html#L560" class="el_method">convertToBytes(BufferedImage, float)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="66" alt="66"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f13">3</td><td class="ctr2" id="g13">3</td><td class="ctr1" id="h8">17</td><td class="ctr2" id="i8">17</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a11"><a href="CompressController.java.html#L844" class="el_method">determineOptimizeLevel(double)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="50" alt="50"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="16" alt="16"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f2">9</td><td class="ctr2" id="g2">9</td><td class="ctr1" id="h13">9</td><td class="ctr2" id="i13">9</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a13"><a href="CompressController.java.html#L593" class="el_method">generateImageHash(PDImageXObject)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="48" alt="48"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="6" alt="6"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f8">4</td><td class="ctr2" id="g8">4</td><td class="ctr1" id="h10">11</td><td class="ctr2" id="i10">11</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a27"><a href="CompressController.java.html#L394" class="el_method">replaceImages(PDDocument, Map, Map, CompressController.CompressionStats)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="45" alt="45"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="6" alt="6"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f9">4</td><td class="ctr2" id="g9">4</td><td class="ctr1" id="h11">10</td><td class="ctr2" id="i11">10</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a18"><a href="CompressController.java.html#L857" class="el_method">incrementOptimizeLevel(int, long, long)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="44" alt="44"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f14">3</td><td class="ctr2" id="g14">3</td><td class="ctr1" id="h17">7</td><td class="ctr2" id="i17">7</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a16"><a href="CompressController.java.html#L334" class="el_method">getOriginalImage(PDDocument, CompressController.ImageReference)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="43" alt="43"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d17"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f17">2</td><td class="ctr2" id="g17">2</td><td class="ctr1" id="h12">10</td><td class="ctr2" id="i12">10</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a3"><a href="CompressController.java.html#L260" class="el_method">calculateImageStats(Map, CompressController.CompressionStats)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="39" alt="39"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="6" alt="6"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f10">4</td><td class="ctr2" id="g10">4</td><td class="ctr1" id="h15">8</td><td class="ctr2" id="i15">8</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a2"><a href="CompressController.java.html#L611" class="el_method">bytesToHexString(byte[])</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="35" alt="35"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d18"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f18">2</td><td class="ctr2" id="g18">2</td><td class="ctr1" id="h21">4</td><td class="ctr2" id="i21">4</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a6"><a href="CompressController.java.html#L365" class="el_method">compressImage(PDDocument, PDImageXObject, int, double, float, boolean)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="31" alt="31"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="6" alt="6"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f11">4</td><td class="ctr2" id="g11">4</td><td class="ctr1" id="h14">9</td><td class="ctr2" id="i14">9</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a0"><a href="CompressController.java.html#L203" class="el_method">addDirectImage(int, COSName, PDImageXObject, Map)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="24" alt="24"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h18">6</td><td class="ctr2" id="i18">6</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a9"><a href="CompressController.java.html#L465" class="el_method">convertToGrayscale(BufferedImage)</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="23" alt="23"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h19">6</td><td class="ctr2" id="i19">6</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a17"><a href="CompressController.java.html#L629" class="el_method">getScaleFactorForLevel(int)</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="16" alt="16"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="7" alt="7"/></td><td class="ctr2" id="e17">0%</td><td class="ctr1" id="f4">7</td><td class="ctr2" id="g4">7</td><td class="ctr1" id="h16">8</td><td class="ctr2" id="i16">8</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a14"><a href="CompressController.java.html#L620" class="el_method">generatMD5(byte[])</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="14" alt="14"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h22">4</td><td class="ctr2" id="i22">4</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a5"><a href="CompressController.java.html#L71" class="el_method">CompressController(CustomPDFDocumentFactory, EndpointConfiguration)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="11" alt="11"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">4</td><td class="ctr2" id="i23">4</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a15"><a href="CompressController.java.html#L642" class="el_method">getJpegQualityForLevel(int)</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="10" alt="10"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="e18">0%</td><td class="ctr1" id="f12">4</td><td class="ctr2" id="g12">4</td><td class="ctr1" id="h20">5</td><td class="ctr2" id="i20">5</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a22"><a href="CompressController.java.html#L252" class="el_method">lambda$checkFormForImages$1(String)</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h24">1</td><td class="ctr2" id="i24">1</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr><tr><td id="a21"><a href="CompressController.java.html#L208" class="el_method">lambda$addDirectImage$0(String)</a></td><td class="bar" id="b25"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g25">1</td><td class="ctr1" id="h25">1</td><td class="ctr2" id="i25">1</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k25">1</td></tr><tr><td id="a28"><a href="CompressController.java.html#L62" class="el_method">static {...}</a></td><td class="bar" id="b26"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c26">0%</td><td class="bar" id="d26"/><td class="ctr2" id="e26">n/a</td><td class="ctr1" id="f26">1</td><td class="ctr2" id="g26">1</td><td class="ctr1" id="h26">1</td><td class="ctr2" id="i26">1</td><td class="ctr1" id="j26">1</td><td class="ctr2" id="k26">1</td></tr><tr><td id="a20"><a href="CompressController.java.html#L190" class="el_method">isImage(PDXObject)</a></td><td class="bar" id="b27"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="3" alt="3"/></td><td class="ctr2" id="c27">0%</td><td class="bar" id="d27"/><td class="ctr2" id="e27">n/a</td><td class="ctr1" id="f27">1</td><td class="ctr2" id="g27">1</td><td class="ctr1" id="h27">1</td><td class="ctr2" id="i27">1</td><td class="ctr1" id="j27">1</td><td class="ctr2" id="k27">1</td></tr><tr><td id="a19"><a href="CompressController.java.html#L194" class="el_method">isForm(PDXObject)</a></td><td class="bar" id="b28"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="3" alt="3"/></td><td class="ctr2" id="c28">0%</td><td class="bar" id="d28"/><td class="ctr2" id="e28">n/a</td><td class="ctr1" id="f28">1</td><td class="ctr2" id="g28">1</td><td class="ctr1" id="h28">1</td><td class="ctr2" id="i28">1</td><td class="ctr1" id="j28">1</td><td class="ctr2" id="k28">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController.java.html
    new file mode 100644
    index 000000000..e8adcbae2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/CompressController.java.html
    @@ -0,0 +1,868 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CompressController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">CompressController.java</span></div><h1>CompressController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.*;
    +import java.awt.image.BufferedImage;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.StandardCopyOption;
    +import java.security.MessageDigest;
    +import java.security.NoSuchAlgorithmException;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.HashMap;
    +import java.util.Iterator;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Map.Entry;
    +
    +import javax.imageio.IIOImage;
    +import javax.imageio.ImageIO;
    +import javax.imageio.ImageWriteParam;
    +import javax.imageio.ImageWriter;
    +import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
    +import javax.imageio.stream.ImageOutputStream;
    +
    +import org.apache.pdfbox.cos.COSName;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDResources;
    +import org.apache.pdfbox.pdmodel.graphics.PDXObject;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.AllArgsConstructor;
    +import lombok.Data;
    +import lombok.EqualsAndHashCode;
    +import lombok.NoArgsConstructor;
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.EndpointConfiguration;
    +import stirling.software.SPDF.model.api.misc.OptimizePdfRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +import stirling.software.SPDF.utils.ProcessExecutor;
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L62">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class CompressController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +    private final boolean qpdfEnabled;
    +
    +    public CompressController(
    +            CustomPDFDocumentFactory pdfDocumentFactory,
    +<span class="nc" id="L71">            EndpointConfiguration endpointConfiguration) {</span>
    +<span class="nc" id="L72">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L73">        this.qpdfEnabled = endpointConfiguration.isGroupEnabled(&quot;qpdf&quot;);</span>
    +<span class="nc" id="L74">    }</span>
    +
    +    @Data
    +    @AllArgsConstructor
    +    @NoArgsConstructor
    +    private static class ImageReference {
    +        int pageNum; // Page number where the image appears
    +        COSName name; // The name used to reference this image
    +    }
    +
    +    @Data
    +    @EqualsAndHashCode(callSuper = true)
    +    @AllArgsConstructor
    +    @NoArgsConstructor
    +    private static class NestedImageReference extends ImageReference {
    +        COSName formName; // Name of the form XObject containing the image
    +        COSName imageName; // Name of the image within the form
    +    }
    +
    +    // Tracks compression stats for reporting
    +<span class="nc" id="L94">    private static class CompressionStats {</span>
    +<span class="nc" id="L95">        int totalImages = 0;</span>
    +<span class="nc" id="L96">        int nestedImages = 0;</span>
    +<span class="nc" id="L97">        int uniqueImagesCount = 0;</span>
    +<span class="nc" id="L98">        int compressedImages = 0;</span>
    +<span class="nc" id="L99">        int skippedImages = 0;</span>
    +<span class="nc" id="L100">        long totalOriginalBytes = 0;</span>
    +<span class="nc" id="L101">        long totalCompressedBytes = 0;</span>
    +    }
    +
    +    public Path compressImagesInPDF(
    +            Path pdfFile, double scaleFactor, float jpegQuality, boolean convertToGrayscale)
    +            throws Exception {
    +<span class="nc" id="L107">        Path newCompressedPDF = Files.createTempFile(&quot;compressedPDF&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L108">        long originalFileSize = Files.size(pdfFile);</span>
    +<span class="nc" id="L109">        log.info(</span>
    +                &quot;Starting image compression with scale factor: {}, JPEG quality: {}, grayscale: {} on file size: {}&quot;,
    +<span class="nc" id="L111">                scaleFactor,</span>
    +<span class="nc" id="L112">                jpegQuality,</span>
    +<span class="nc" id="L113">                convertToGrayscale,</span>
    +<span class="nc" id="L114">                GeneralUtils.formatBytes(originalFileSize));</span>
    +
    +<span class="nc" id="L116">        try (PDDocument doc = pdfDocumentFactory.load(pdfFile)) {</span>
    +            // Find all unique images in the document
    +<span class="nc" id="L118">            Map&lt;String, List&lt;ImageReference&gt;&gt; uniqueImages = findImages(doc);</span>
    +
    +            // Get statistics
    +<span class="nc" id="L121">            CompressionStats stats = new CompressionStats();</span>
    +<span class="nc" id="L122">            stats.uniqueImagesCount = uniqueImages.size();</span>
    +<span class="nc" id="L123">            calculateImageStats(uniqueImages, stats);</span>
    +
    +            // Create compressed versions of unique images
    +<span class="nc" id="L126">            Map&lt;String, PDImageXObject&gt; compressedVersions =</span>
    +<span class="nc" id="L127">                    createCompressedImages(</span>
    +                            doc, uniqueImages, scaleFactor, jpegQuality, convertToGrayscale, stats);
    +
    +            // Replace all instances with compressed versions
    +<span class="nc" id="L131">            replaceImages(doc, uniqueImages, compressedVersions, stats);</span>
    +
    +            // Log compression statistics
    +<span class="nc" id="L134">            logCompressionStats(stats, originalFileSize);</span>
    +
    +            // Free memory before saving
    +<span class="nc" id="L137">            compressedVersions.clear();</span>
    +<span class="nc" id="L138">            uniqueImages.clear();</span>
    +
    +<span class="nc" id="L140">            log.info(&quot;Saving compressed PDF to {}&quot;, newCompressedPDF.toString());</span>
    +<span class="nc" id="L141">            doc.save(newCompressedPDF.toString());</span>
    +
    +            // Log overall file size reduction
    +<span class="nc" id="L144">            long compressedFileSize = Files.size(newCompressedPDF);</span>
    +<span class="nc" id="L145">            double overallReduction = 100.0 - ((compressedFileSize * 100.0) / originalFileSize);</span>
    +<span class="nc" id="L146">            log.info(</span>
    +                    &quot;Overall PDF compression: {} → {} (reduced by {}%)&quot;,
    +<span class="nc" id="L148">                    GeneralUtils.formatBytes(originalFileSize),</span>
    +<span class="nc" id="L149">                    GeneralUtils.formatBytes(compressedFileSize),</span>
    +<span class="nc" id="L150">                    String.format(&quot;%.1f&quot;, overallReduction));</span>
    +<span class="nc" id="L151">            return newCompressedPDF;</span>
    +        }
    +    }
    +
    +    // Find all images in the document, both direct and nested within forms
    +    private Map&lt;String, List&lt;ImageReference&gt;&gt; findImages(PDDocument doc) throws IOException {
    +<span class="nc" id="L157">        Map&lt;String, List&lt;ImageReference&gt;&gt; uniqueImages = new HashMap&lt;&gt;();</span>
    +
    +        // Scan through all pages in the document
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">        for (int pageNum = 0; pageNum &lt; doc.getNumberOfPages(); pageNum++) {</span>
    +<span class="nc" id="L161">            PDPage page = doc.getPage(pageNum);</span>
    +<span class="nc" id="L162">            PDResources res = page.getResources();</span>
    +<span class="nc bnc" id="L163" title="All 4 branches missed.">            if (res == null || res.getXObjectNames() == null) continue;</span>
    +
    +            // Process all XObjects on the page
    +<span class="nc bnc" id="L166" title="All 2 branches missed.">            for (COSName name : res.getXObjectNames()) {</span>
    +<span class="nc" id="L167">                PDXObject xobj = res.getXObject(name);</span>
    +
    +                // Direct image
    +<span class="nc bnc" id="L170" title="All 2 branches missed.">                if (isImage(xobj)) {</span>
    +<span class="nc" id="L171">                    addDirectImage(pageNum, name, (PDImageXObject) xobj, uniqueImages);</span>
    +<span class="nc" id="L172">                    log.info(</span>
    +                            &quot;Found direct image '{}' on page {} - {}x{}&quot;,
    +<span class="nc" id="L174">                            name.getName(),</span>
    +<span class="nc" id="L175">                            pageNum + 1,</span>
    +<span class="nc" id="L176">                            ((PDImageXObject) xobj).getWidth(),</span>
    +<span class="nc" id="L177">                            ((PDImageXObject) xobj).getHeight());</span>
    +                }
    +                // Form XObject that may contain nested images
    +<span class="nc bnc" id="L180" title="All 2 branches missed.">                else if (isForm(xobj)) {</span>
    +<span class="nc" id="L181">                    checkFormForImages(pageNum, name, (PDFormXObject) xobj, uniqueImages);</span>
    +                }
    +<span class="nc" id="L183">            }</span>
    +        }
    +
    +<span class="nc" id="L186">        return uniqueImages;</span>
    +    }
    +
    +    private boolean isImage(PDXObject xobj) {
    +<span class="nc" id="L190">        return xobj instanceof PDImageXObject;</span>
    +    }
    +
    +    private boolean isForm(PDXObject xobj) {
    +<span class="nc" id="L194">        return xobj instanceof PDFormXObject;</span>
    +    }
    +
    +    private ImageReference addDirectImage(
    +            int pageNum,
    +            COSName name,
    +            PDImageXObject image,
    +            Map&lt;String, List&lt;ImageReference&gt;&gt; uniqueImages)
    +            throws IOException {
    +<span class="nc" id="L203">        ImageReference ref = new ImageReference();</span>
    +<span class="nc" id="L204">        ref.pageNum = pageNum;</span>
    +<span class="nc" id="L205">        ref.name = name;</span>
    +
    +<span class="nc" id="L207">        String imageHash = generateImageHash(image);</span>
    +<span class="nc" id="L208">        uniqueImages.computeIfAbsent(imageHash, k -&gt; new ArrayList&lt;&gt;()).add(ref);</span>
    +
    +<span class="nc" id="L210">        return ref;</span>
    +    }
    +
    +    // Look for images inside form XObjects
    +    private void checkFormForImages(
    +            int pageNum,
    +            COSName formName,
    +            PDFormXObject formXObj,
    +            Map&lt;String, List&lt;ImageReference&gt;&gt; uniqueImages)
    +            throws IOException {
    +<span class="nc" id="L220">        PDResources formResources = formXObj.getResources();</span>
    +<span class="nc bnc" id="L221" title="All 4 branches missed.">        if (formResources == null || formResources.getXObjectNames() == null) {</span>
    +<span class="nc" id="L222">            return;</span>
    +        }
    +
    +<span class="nc" id="L225">        log.info(</span>
    +                &quot;Checking form XObject '{}' on page {} for nested images&quot;,
    +<span class="nc" id="L227">                formName.getName(),</span>
    +<span class="nc" id="L228">                pageNum + 1);</span>
    +
    +        // Process all XObjects within the form
    +<span class="nc bnc" id="L231" title="All 2 branches missed.">        for (COSName nestedName : formResources.getXObjectNames()) {</span>
    +<span class="nc" id="L232">            PDXObject nestedXobj = formResources.getXObject(nestedName);</span>
    +
    +<span class="nc bnc" id="L234" title="All 2 branches missed.">            if (isImage(nestedXobj)) {</span>
    +<span class="nc" id="L235">                PDImageXObject nestedImage = (PDImageXObject) nestedXobj;</span>
    +
    +<span class="nc" id="L237">                log.info(</span>
    +                        &quot;Found nested image '{}' in form '{}' on page {} - {}x{}&quot;,
    +<span class="nc" id="L239">                        nestedName.getName(),</span>
    +<span class="nc" id="L240">                        formName.getName(),</span>
    +<span class="nc" id="L241">                        pageNum + 1,</span>
    +<span class="nc" id="L242">                        nestedImage.getWidth(),</span>
    +<span class="nc" id="L243">                        nestedImage.getHeight());</span>
    +
    +                // Create specialized reference for the nested image
    +<span class="nc" id="L246">                NestedImageReference nestedRef = new NestedImageReference();</span>
    +<span class="nc" id="L247">                nestedRef.pageNum = pageNum;</span>
    +<span class="nc" id="L248">                nestedRef.formName = formName;</span>
    +<span class="nc" id="L249">                nestedRef.imageName = nestedName;</span>
    +
    +<span class="nc" id="L251">                String imageHash = generateImageHash(nestedImage);</span>
    +<span class="nc" id="L252">                uniqueImages.computeIfAbsent(imageHash, k -&gt; new ArrayList&lt;&gt;()).add(nestedRef);</span>
    +            }
    +<span class="nc" id="L254">        }</span>
    +<span class="nc" id="L255">    }</span>
    +
    +    // Count total images and nested images
    +    private void calculateImageStats(
    +            Map&lt;String, List&lt;ImageReference&gt;&gt; uniqueImages, CompressionStats stats) {
    +<span class="nc bnc" id="L260" title="All 2 branches missed.">        for (List&lt;ImageReference&gt; references : uniqueImages.values()) {</span>
    +<span class="nc bnc" id="L261" title="All 2 branches missed.">            for (ImageReference ref : references) {</span>
    +<span class="nc" id="L262">                stats.totalImages++;</span>
    +<span class="nc bnc" id="L263" title="All 2 branches missed.">                if (ref instanceof NestedImageReference) {</span>
    +<span class="nc" id="L264">                    stats.nestedImages++;</span>
    +                }
    +<span class="nc" id="L266">            }</span>
    +<span class="nc" id="L267">        }</span>
    +<span class="nc" id="L268">    }</span>
    +
    +    // Create compressed versions of all unique images
    +    private Map&lt;String, PDImageXObject&gt; createCompressedImages(
    +            PDDocument doc,
    +            Map&lt;String, List&lt;ImageReference&gt;&gt; uniqueImages,
    +            double scaleFactor,
    +            float jpegQuality,
    +            boolean convertToGrayscale,
    +            CompressionStats stats)
    +            throws IOException {
    +
    +<span class="nc" id="L280">        Map&lt;String, PDImageXObject&gt; compressedVersions = new HashMap&lt;&gt;();</span>
    +
    +        // Process each unique image exactly once
    +<span class="nc bnc" id="L283" title="All 2 branches missed.">        for (Entry&lt;String, List&lt;ImageReference&gt;&gt; entry : uniqueImages.entrySet()) {</span>
    +<span class="nc" id="L284">            String imageHash = entry.getKey();</span>
    +<span class="nc" id="L285">            List&lt;ImageReference&gt; references = entry.getValue();</span>
    +
    +<span class="nc bnc" id="L287" title="All 2 branches missed.">            if (references.isEmpty()) continue;</span>
    +
    +            // Get the first instance of this image
    +<span class="nc" id="L290">            PDImageXObject originalImage = getOriginalImage(doc, references.get(0));</span>
    +
    +            // Track original size
    +<span class="nc" id="L293">            int originalSize = (int) originalImage.getCOSObject().getLength();</span>
    +<span class="nc" id="L294">            stats.totalOriginalBytes += originalSize;</span>
    +
    +            // Process this unique image
    +<span class="nc" id="L297">            PDImageXObject compressedImage =</span>
    +<span class="nc" id="L298">                    compressImage(</span>
    +                            doc,
    +                            originalImage,
    +                            originalSize,
    +                            scaleFactor,
    +                            jpegQuality,
    +                            convertToGrayscale);
    +
    +<span class="nc bnc" id="L306" title="All 2 branches missed.">            if (compressedImage != null) {</span>
    +                // Store the compressed version in our map
    +<span class="nc" id="L308">                compressedVersions.put(imageHash, compressedImage);</span>
    +<span class="nc" id="L309">                stats.compressedImages++;</span>
    +
    +                // Update compression stats
    +<span class="nc" id="L312">                int compressedSize = (int) compressedImage.getCOSObject().getLength();</span>
    +<span class="nc" id="L313">                stats.totalCompressedBytes += compressedSize * references.size();</span>
    +
    +<span class="nc" id="L315">                double reductionPercentage = 100.0 - ((compressedSize * 100.0) / originalSize);</span>
    +<span class="nc" id="L316">                log.info(</span>
    +                        &quot;Image hash {}: Compressed from {} to {} (reduced by {}%)&quot;,
    +                        imageHash,
    +<span class="nc" id="L319">                        GeneralUtils.formatBytes(originalSize),</span>
    +<span class="nc" id="L320">                        GeneralUtils.formatBytes(compressedSize),</span>
    +<span class="nc" id="L321">                        String.format(&quot;%.1f&quot;, reductionPercentage));</span>
    +<span class="nc" id="L322">            } else {</span>
    +<span class="nc" id="L323">                log.info(&quot;Image hash {}: Not suitable for compression, skipping&quot;, imageHash);</span>
    +<span class="nc" id="L324">                stats.totalCompressedBytes += originalSize * references.size();</span>
    +<span class="nc" id="L325">                stats.skippedImages++;</span>
    +            }
    +<span class="nc" id="L327">        }</span>
    +
    +<span class="nc" id="L329">        return compressedVersions;</span>
    +    }
    +
    +    // Get original image from a reference
    +    private PDImageXObject getOriginalImage(PDDocument doc, ImageReference ref) throws IOException {
    +<span class="nc bnc" id="L334" title="All 2 branches missed.">        if (ref instanceof NestedImageReference) {</span>
    +            // Get the nested image from within a form XObject
    +<span class="nc" id="L336">            NestedImageReference nestedRef = (NestedImageReference) ref;</span>
    +<span class="nc" id="L337">            PDPage page = doc.getPage(nestedRef.pageNum);</span>
    +<span class="nc" id="L338">            PDResources pageResources = page.getResources();</span>
    +
    +            // Get the form XObject
    +<span class="nc" id="L341">            PDFormXObject formXObj = (PDFormXObject) pageResources.getXObject(nestedRef.formName);</span>
    +
    +            // Get the nested image from the form's resources
    +<span class="nc" id="L344">            PDResources formResources = formXObj.getResources();</span>
    +<span class="nc" id="L345">            return (PDImageXObject) formResources.getXObject(nestedRef.imageName);</span>
    +        } else {
    +            // Get direct image from page resources
    +<span class="nc" id="L348">            PDPage page = doc.getPage(ref.pageNum);</span>
    +<span class="nc" id="L349">            PDResources resources = page.getResources();</span>
    +<span class="nc" id="L350">            return (PDImageXObject) resources.getXObject(ref.name);</span>
    +        }
    +    }
    +
    +    // Try to compress an image if it makes sense
    +    private PDImageXObject compressImage(
    +            PDDocument doc,
    +            PDImageXObject originalImage,
    +            int originalSize,
    +            double scaleFactor,
    +            float jpegQuality,
    +            boolean convertToGrayscale)
    +            throws IOException {
    +
    +        // Process and compress the image
    +<span class="nc" id="L365">        BufferedImage processedImage =</span>
    +<span class="nc" id="L366">                processAndCompressImage(</span>
    +                        originalImage, scaleFactor, jpegQuality, convertToGrayscale);
    +
    +<span class="nc bnc" id="L369" title="All 2 branches missed.">        if (processedImage == null) {</span>
    +<span class="nc" id="L370">            return null;</span>
    +        }
    +
    +        // Convert to bytes for storage
    +<span class="nc" id="L374">        byte[] compressedData = convertToBytes(processedImage, jpegQuality);</span>
    +
    +        // Check if compression is beneficial
    +<span class="nc bnc" id="L377" title="All 4 branches missed.">        if (compressedData.length &lt; originalSize || convertToGrayscale) {</span>
    +            // Create a compressed version
    +<span class="nc" id="L379">            return PDImageXObject.createFromByteArray(</span>
    +<span class="nc" id="L380">                    doc, compressedData, originalImage.getCOSObject().toString());</span>
    +        }
    +
    +<span class="nc" id="L383">        return null;</span>
    +    }
    +
    +    // Replace all instances of original images with their compressed versions
    +    private void replaceImages(
    +            PDDocument doc,
    +            Map&lt;String, List&lt;ImageReference&gt;&gt; uniqueImages,
    +            Map&lt;String, PDImageXObject&gt; compressedVersions,
    +            CompressionStats stats)
    +            throws IOException {
    +
    +<span class="nc bnc" id="L394" title="All 2 branches missed.">        for (Entry&lt;String, List&lt;ImageReference&gt;&gt; entry : uniqueImages.entrySet()) {</span>
    +<span class="nc" id="L395">            String imageHash = entry.getKey();</span>
    +<span class="nc" id="L396">            List&lt;ImageReference&gt; references = entry.getValue();</span>
    +
    +            // Skip if no compressed version exists
    +<span class="nc" id="L399">            PDImageXObject compressedImage = compressedVersions.get(imageHash);</span>
    +<span class="nc bnc" id="L400" title="All 2 branches missed.">            if (compressedImage == null) continue;</span>
    +
    +            // Replace ALL instances with the compressed version
    +<span class="nc bnc" id="L403" title="All 2 branches missed.">            for (ImageReference ref : references) {</span>
    +<span class="nc" id="L404">                replaceImageReference(doc, ref, compressedImage);</span>
    +<span class="nc" id="L405">            }</span>
    +<span class="nc" id="L406">        }</span>
    +<span class="nc" id="L407">    }</span>
    +
    +    // Replace a specific image reference with a compressed version
    +    private void replaceImageReference(
    +            PDDocument doc, ImageReference ref, PDImageXObject compressedImage) throws IOException {
    +<span class="nc bnc" id="L412" title="All 2 branches missed.">        if (ref instanceof NestedImageReference) {</span>
    +            // Replace nested image within form XObject
    +<span class="nc" id="L414">            NestedImageReference nestedRef = (NestedImageReference) ref;</span>
    +<span class="nc" id="L415">            PDPage page = doc.getPage(nestedRef.pageNum);</span>
    +<span class="nc" id="L416">            PDResources pageResources = page.getResources();</span>
    +
    +            // Get the form XObject
    +<span class="nc" id="L419">            PDFormXObject formXObj = (PDFormXObject) pageResources.getXObject(nestedRef.formName);</span>
    +
    +            // Replace the nested image in the form's resources
    +<span class="nc" id="L422">            PDResources formResources = formXObj.getResources();</span>
    +<span class="nc" id="L423">            formResources.put(nestedRef.imageName, compressedImage);</span>
    +
    +<span class="nc" id="L425">            log.info(</span>
    +                    &quot;Replaced nested image '{}' in form '{}' on page {} with compressed version&quot;,
    +<span class="nc" id="L427">                    nestedRef.imageName.getName(),</span>
    +<span class="nc" id="L428">                    nestedRef.formName.getName(),</span>
    +<span class="nc" id="L429">                    nestedRef.pageNum + 1);</span>
    +<span class="nc" id="L430">        } else {</span>
    +            // Replace direct image in page resources
    +<span class="nc" id="L432">            PDPage page = doc.getPage(ref.pageNum);</span>
    +<span class="nc" id="L433">            PDResources resources = page.getResources();</span>
    +<span class="nc" id="L434">            resources.put(ref.name, compressedImage);</span>
    +
    +<span class="nc" id="L436">            log.info(&quot;Replaced direct image on page {} with compressed version&quot;, ref.pageNum + 1);</span>
    +        }
    +<span class="nc" id="L438">    }</span>
    +
    +    // Log final stats about the compression
    +    private void logCompressionStats(CompressionStats stats, long originalFileSize) {
    +        // Calculate image reduction percentage
    +        double overallImageReduction =
    +<span class="nc bnc" id="L444" title="All 2 branches missed.">                stats.totalOriginalBytes &gt; 0</span>
    +<span class="nc" id="L445">                        ? 100.0 - ((stats.totalCompressedBytes * 100.0) / stats.totalOriginalBytes)</span>
    +<span class="nc" id="L446">                        : 0;</span>
    +
    +<span class="nc" id="L448">        int duplicatedImages = stats.totalImages - stats.uniqueImagesCount;</span>
    +
    +<span class="nc" id="L450">        log.info(</span>
    +                &quot;Image compression summary - Total unique: {}, Compressed: {}, Skipped: {}, Duplicates: {}, Nested: {}&quot;,
    +<span class="nc" id="L452">                stats.uniqueImagesCount,</span>
    +<span class="nc" id="L453">                stats.compressedImages,</span>
    +<span class="nc" id="L454">                stats.skippedImages,</span>
    +<span class="nc" id="L455">                duplicatedImages,</span>
    +<span class="nc" id="L456">                stats.nestedImages);</span>
    +<span class="nc" id="L457">        log.info(</span>
    +                &quot;Total original image size: {}, compressed: {} (reduced by {}%)&quot;,
    +<span class="nc" id="L459">                GeneralUtils.formatBytes(stats.totalOriginalBytes),</span>
    +<span class="nc" id="L460">                GeneralUtils.formatBytes(stats.totalCompressedBytes),</span>
    +<span class="nc" id="L461">                String.format(&quot;%.1f&quot;, overallImageReduction));</span>
    +<span class="nc" id="L462">    }</span>
    +
    +    private BufferedImage convertToGrayscale(BufferedImage image) {
    +<span class="nc" id="L465">        BufferedImage grayImage =</span>
    +                new BufferedImage(
    +<span class="nc" id="L467">                        image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);</span>
    +
    +<span class="nc" id="L469">        Graphics2D g = grayImage.createGraphics();</span>
    +<span class="nc" id="L470">        g.drawImage(image, 0, 0, null);</span>
    +<span class="nc" id="L471">        g.dispose();</span>
    +
    +<span class="nc" id="L473">        return grayImage;</span>
    +    }
    +
    +    // Resize and optionally convert to grayscale
    +    private BufferedImage processAndCompressImage(
    +            PDImageXObject image, double scaleFactor, float jpegQuality, boolean convertToGrayscale)
    +            throws IOException {
    +<span class="nc" id="L480">        BufferedImage bufferedImage = image.getImage();</span>
    +<span class="nc" id="L481">        int originalWidth = bufferedImage.getWidth();</span>
    +<span class="nc" id="L482">        int originalHeight = bufferedImage.getHeight();</span>
    +
    +        // Minimum dimensions to preserve reasonable quality
    +<span class="nc" id="L485">        int MIN_WIDTH = 400;</span>
    +<span class="nc" id="L486">        int MIN_HEIGHT = 400;</span>
    +
    +<span class="nc" id="L488">        log.info(&quot;Original dimensions: {}x{}&quot;, originalWidth, originalHeight);</span>
    +
    +        // Skip if already small enough
    +<span class="nc bnc" id="L491" title="All 6 branches missed.">        if ((originalWidth &lt;= MIN_WIDTH || originalHeight &lt;= MIN_HEIGHT) &amp;&amp; !convertToGrayscale) {</span>
    +<span class="nc" id="L492">            log.info(&quot;Skipping - below minimum dimensions threshold&quot;);</span>
    +<span class="nc" id="L493">            return null;</span>
    +        }
    +
    +        // Convert to grayscale first if requested (before resizing for better quality)
    +<span class="nc bnc" id="L497" title="All 2 branches missed.">        if (convertToGrayscale) {</span>
    +<span class="nc" id="L498">            bufferedImage = convertToGrayscale(bufferedImage);</span>
    +<span class="nc" id="L499">            log.info(&quot;Converted image to grayscale&quot;);</span>
    +        }
    +
    +        // Adjust scale factor for very large or very small images
    +<span class="nc" id="L503">        double adjustedScaleFactor = scaleFactor;</span>
    +<span class="nc bnc" id="L504" title="All 4 branches missed.">        if (originalWidth &gt; 3000 || originalHeight &gt; 3000) {</span>
    +            // More aggressive for very large images
    +<span class="nc" id="L506">            adjustedScaleFactor = Math.min(scaleFactor, 0.75);</span>
    +<span class="nc" id="L507">            log.info(&quot;Very large image, using more aggressive scale: {}&quot;, adjustedScaleFactor);</span>
    +<span class="nc bnc" id="L508" title="All 4 branches missed.">        } else if (originalWidth &lt; 1000 || originalHeight &lt; 1000) {</span>
    +            // More conservative for smaller images
    +<span class="nc" id="L510">            adjustedScaleFactor = Math.max(scaleFactor, 0.9);</span>
    +<span class="nc" id="L511">            log.info(&quot;Smaller image, using conservative scale: {}&quot;, adjustedScaleFactor);</span>
    +        }
    +
    +<span class="nc" id="L514">        int newWidth = (int) (originalWidth * adjustedScaleFactor);</span>
    +<span class="nc" id="L515">        int newHeight = (int) (originalHeight * adjustedScaleFactor);</span>
    +
    +        // Ensure minimum dimensions
    +<span class="nc" id="L518">        newWidth = Math.max(newWidth, MIN_WIDTH);</span>
    +<span class="nc" id="L519">        newHeight = Math.max(newHeight, MIN_HEIGHT);</span>
    +
    +        // Skip if change is negligible
    +<span class="nc bnc" id="L522" title="All 6 branches missed.">        if ((double) newWidth / originalWidth &gt; 0.95</span>
    +                &amp;&amp; (double) newHeight / originalHeight &gt; 0.95
    +                &amp;&amp; !convertToGrayscale) {
    +<span class="nc" id="L525">            log.info(&quot;Change too small, skipping compression&quot;);</span>
    +<span class="nc" id="L526">            return null;</span>
    +        }
    +
    +<span class="nc" id="L529">        log.info(</span>
    +                &quot;Resizing to {}x{} ({}% of original)&quot;,
    +<span class="nc" id="L531">                newWidth, newHeight, Math.round((newWidth * 100.0) / originalWidth));</span>
    +
    +        BufferedImage scaledImage;
    +<span class="nc bnc" id="L534" title="All 2 branches missed.">        if (convertToGrayscale) {</span>
    +            // If already grayscale, maintain the grayscale format
    +<span class="nc" id="L536">            scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_BYTE_GRAY);</span>
    +        } else {
    +            // Otherwise use original color model
    +<span class="nc" id="L539">            scaledImage =</span>
    +                    new BufferedImage(
    +                            newWidth,
    +                            newHeight,
    +<span class="nc bnc" id="L543" title="All 2 branches missed.">                            bufferedImage.getColorModel().hasAlpha()</span>
    +<span class="nc" id="L544">                                    ? BufferedImage.TYPE_INT_ARGB</span>
    +<span class="nc" id="L545">                                    : BufferedImage.TYPE_INT_RGB);</span>
    +        }
    +<span class="nc" id="L547">        Graphics2D g2d = scaledImage.createGraphics();</span>
    +<span class="nc" id="L548">        g2d.setRenderingHint(</span>
    +                RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    +<span class="nc" id="L550">        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);</span>
    +<span class="nc" id="L551">        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);</span>
    +<span class="nc" id="L552">        g2d.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null);</span>
    +<span class="nc" id="L553">        g2d.dispose();</span>
    +
    +<span class="nc" id="L555">        return scaledImage;</span>
    +    }
    +
    +    // Convert image to byte array with quality settings
    +    private byte[] convertToBytes(BufferedImage scaledImage, float jpegQuality) throws IOException {
    +<span class="nc bnc" id="L560" title="All 2 branches missed.">        String format = scaledImage.getColorModel().hasAlpha() ? &quot;png&quot; : &quot;jpeg&quot;;</span>
    +<span class="nc" id="L561">        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();</span>
    +
    +<span class="nc bnc" id="L563" title="All 2 branches missed.">        if (&quot;jpeg&quot;.equals(format)) {</span>
    +            // Get the best available JPEG writer
    +<span class="nc" id="L565">            Iterator&lt;ImageWriter&gt; writers = ImageIO.getImageWritersByFormatName(&quot;jpeg&quot;);</span>
    +<span class="nc" id="L566">            ImageWriter writer = writers.next();</span>
    +
    +<span class="nc" id="L568">            JPEGImageWriteParam param = (JPEGImageWriteParam) writer.getDefaultWriteParam();</span>
    +
    +            // Set compression parameters
    +<span class="nc" id="L571">            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);</span>
    +<span class="nc" id="L572">            param.setCompressionQuality(jpegQuality);</span>
    +<span class="nc" id="L573">            param.setOptimizeHuffmanTables(true); // Better compression</span>
    +<span class="nc" id="L574">            param.setProgressiveMode(ImageWriteParam.MODE_DEFAULT); // Progressive scanning</span>
    +
    +            // Write compressed image
    +<span class="nc" id="L577">            try (ImageOutputStream ios = ImageIO.createImageOutputStream(outputStream)) {</span>
    +<span class="nc" id="L578">                writer.setOutput(ios);</span>
    +<span class="nc" id="L579">                writer.write(null, new IIOImage(scaledImage, null, null), param);</span>
    +            }
    +<span class="nc" id="L581">            writer.dispose();</span>
    +<span class="nc" id="L582">        } else {</span>
    +<span class="nc" id="L583">            ImageIO.write(scaledImage, format, outputStream);</span>
    +        }
    +
    +<span class="nc" id="L586">        return outputStream.toByteArray();</span>
    +    }
    +
    +    // Hash function to identify identical images
    +    private String generateImageHash(PDImageXObject image) {
    +        try {
    +            // Create a stream for the raw stream data
    +<span class="nc" id="L593">            try (InputStream stream = image.getCOSObject().createRawInputStream()) {</span>
    +                // Read up to first 8KB of data for the hash
    +<span class="nc" id="L595">                byte[] buffer = new byte[8192];</span>
    +<span class="nc" id="L596">                int bytesRead = stream.read(buffer);</span>
    +<span class="nc bnc" id="L597" title="All 2 branches missed.">                if (bytesRead &gt; 0) {</span>
    +                    byte[] dataToHash =
    +<span class="nc bnc" id="L599" title="All 2 branches missed.">                            bytesRead == buffer.length ? buffer : Arrays.copyOf(buffer, bytesRead);</span>
    +<span class="nc" id="L600">                    return bytesToHexString(generatMD5(dataToHash));</span>
    +                }
    +<span class="nc" id="L602">                return &quot;empty-stream&quot;;</span>
    +<span class="nc bnc" id="L603" title="All 2 branches missed.">            }</span>
    +<span class="nc" id="L604">        } catch (Exception e) {</span>
    +<span class="nc" id="L605">            log.error(&quot;Error generating image hash&quot;, e);</span>
    +<span class="nc" id="L606">            return &quot;fallback-&quot; + System.identityHashCode(image);</span>
    +        }
    +    }
    +
    +    private String bytesToHexString(byte[] bytes) {
    +<span class="nc" id="L611">        StringBuilder sb = new StringBuilder();</span>
    +<span class="nc bnc" id="L612" title="All 2 branches missed.">        for (byte b : bytes) {</span>
    +<span class="nc" id="L613">            sb.append(String.format(&quot;%02x&quot;, b));</span>
    +        }
    +<span class="nc" id="L615">        return sb.toString();</span>
    +    }
    +
    +    private byte[] generatMD5(byte[] data) throws IOException {
    +        try {
    +<span class="nc" id="L620">            MessageDigest md = MessageDigest.getInstance(&quot;MD5&quot;);</span>
    +<span class="nc" id="L621">            return md.digest(data); // Get the MD5 hash of the image bytes</span>
    +<span class="nc" id="L622">        } catch (NoSuchAlgorithmException e) {</span>
    +<span class="nc" id="L623">            throw new RuntimeException(&quot;MD5 algorithm not available&quot;, e);</span>
    +        }
    +    }
    +
    +    // Scale factors for different optimization levels
    +    private double getScaleFactorForLevel(int optimizeLevel) {
    +<span class="nc bnc" id="L629" title="All 7 branches missed.">        return switch (optimizeLevel) {</span>
    +<span class="nc" id="L630">            case 4 -&gt; 0.9; // 90% - lite compression</span>
    +<span class="nc" id="L631">            case 5 -&gt; 0.8; // 80% - lite compression</span>
    +<span class="nc" id="L632">            case 6 -&gt; 0.7; // 70% - lite compression</span>
    +<span class="nc" id="L633">            case 7 -&gt; 0.6; // 60% - intense compression</span>
    +<span class="nc" id="L634">            case 8 -&gt; 0.5; // 50% - intense compression</span>
    +<span class="nc" id="L635">            case 9, 10 -&gt; 0.4; // 40% - intense compression</span>
    +<span class="nc" id="L636">            default -&gt; 1.0; // No scaling for levels 1-3</span>
    +        };
    +    }
    +
    +    // JPEG quality for different optimization levels
    +    private float getJpegQualityForLevel(int optimizeLevel) {
    +<span class="nc bnc" id="L642" title="All 4 branches missed.">        return switch (optimizeLevel) {</span>
    +<span class="nc" id="L643">            case 7 -&gt; 0.8f; // 80% quality</span>
    +<span class="nc" id="L644">            case 8 -&gt; 0.6f; // 60% quality</span>
    +<span class="nc" id="L645">            case 9, 10 -&gt; 0.4f; // 40% quality</span>
    +<span class="nc" id="L646">            default -&gt; 0.7f; // 70% quality for levels 1-6</span>
    +        };
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/compress-pdf&quot;)
    +    @Operation(
    +            summary = &quot;Optimize PDF file&quot;,
    +            description =
    +                    &quot;This endpoint accepts a PDF file and optimizes it based on the provided&quot;
    +                            + &quot; parameters. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; optimizePdf(@ModelAttribute OptimizePdfRequest request)
    +            throws Exception {
    +<span class="nc" id="L658">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L659">        Integer optimizeLevel = request.getOptimizeLevel();</span>
    +<span class="nc" id="L660">        String expectedOutputSizeString = request.getExpectedOutputSize();</span>
    +<span class="nc" id="L661">        Boolean convertToGrayscale = request.getGrayscale();</span>
    +<span class="nc bnc" id="L662" title="All 4 branches missed.">        if (expectedOutputSizeString == null &amp;&amp; optimizeLevel == null) {</span>
    +<span class="nc" id="L663">            throw new Exception(&quot;Both expected output size and optimize level are not specified&quot;);</span>
    +        }
    +
    +<span class="nc" id="L666">        Long expectedOutputSize = 0L;</span>
    +<span class="nc" id="L667">        boolean autoMode = false;</span>
    +<span class="nc bnc" id="L668" title="All 4 branches missed.">        if (expectedOutputSizeString != null &amp;&amp; expectedOutputSizeString.length() &gt; 1) {</span>
    +<span class="nc" id="L669">            expectedOutputSize = GeneralUtils.convertSizeToBytes(expectedOutputSizeString);</span>
    +<span class="nc" id="L670">            autoMode = true;</span>
    +        }
    +
    +        // Create initial input file
    +<span class="nc" id="L674">        Path originalFile = Files.createTempFile(&quot;original_&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L675">        inputFile.transferTo(originalFile.toFile());</span>
    +<span class="nc" id="L676">        long inputFileSize = Files.size(originalFile);</span>
    +
    +<span class="nc" id="L678">        Path currentFile = Files.createTempFile(&quot;working_&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L679">        Files.copy(originalFile, currentFile, StandardCopyOption.REPLACE_EXISTING);</span>
    +
    +        // Keep track of all temporary files for cleanup
    +<span class="nc" id="L682">        List&lt;Path&gt; tempFiles = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L683">        tempFiles.add(originalFile);</span>
    +<span class="nc" id="L684">        tempFiles.add(currentFile);</span>
    +        try {
    +<span class="nc bnc" id="L686" title="All 2 branches missed.">            if (autoMode) {</span>
    +<span class="nc" id="L687">                double sizeReductionRatio = expectedOutputSize / (double) inputFileSize;</span>
    +<span class="nc" id="L688">                optimizeLevel = determineOptimizeLevel(sizeReductionRatio);</span>
    +            }
    +
    +<span class="nc" id="L691">            boolean sizeMet = false;</span>
    +<span class="nc" id="L692">            boolean imageCompressionApplied = false;</span>
    +<span class="nc" id="L693">            boolean qpdfCompressionApplied = false;</span>
    +
    +<span class="nc bnc" id="L695" title="All 4 branches missed.">            if (qpdfEnabled &amp;&amp; optimizeLevel &lt;= 3) {</span>
    +<span class="nc" id="L696">                optimizeLevel = 4;</span>
    +            }
    +
    +<span class="nc bnc" id="L699" title="All 4 branches missed.">            while (!sizeMet &amp;&amp; optimizeLevel &lt;= 9) {</span>
    +                // Apply image compression for levels 4-9
    +<span class="nc bnc" id="L701" title="All 6 branches missed.">                if ((optimizeLevel &gt;= 4 || Boolean.TRUE.equals(convertToGrayscale))</span>
    +                        &amp;&amp; !imageCompressionApplied) {
    +<span class="nc" id="L703">                    double scaleFactor = getScaleFactorForLevel(optimizeLevel);</span>
    +<span class="nc" id="L704">                    float jpegQuality = getJpegQualityForLevel(optimizeLevel);</span>
    +
    +                    // Compress images
    +<span class="nc" id="L707">                    Path compressedImageFile =</span>
    +<span class="nc" id="L708">                            compressImagesInPDF(</span>
    +                                    currentFile,
    +                                    scaleFactor,
    +                                    jpegQuality,
    +<span class="nc" id="L712">                                    Boolean.TRUE.equals(convertToGrayscale));</span>
    +
    +<span class="nc" id="L714">                    tempFiles.add(compressedImageFile);</span>
    +<span class="nc" id="L715">                    currentFile = compressedImageFile;</span>
    +<span class="nc" id="L716">                    imageCompressionApplied = true;</span>
    +                }
    +
    +                // Apply QPDF compression for all levels
    +<span class="nc bnc" id="L720" title="All 4 branches missed.">                if (!qpdfCompressionApplied &amp;&amp; qpdfEnabled) {</span>
    +<span class="nc" id="L721">                    applyQpdfCompression(request, optimizeLevel, currentFile, tempFiles);</span>
    +<span class="nc" id="L722">                    qpdfCompressionApplied = true;</span>
    +<span class="nc bnc" id="L723" title="All 2 branches missed.">                } else if (!qpdfCompressionApplied) {</span>
    +                    // If QPDF is disabled, mark as applied and log
    +<span class="nc bnc" id="L725" title="All 2 branches missed.">                    if (!qpdfEnabled) {</span>
    +<span class="nc" id="L726">                        log.info(&quot;Skipping QPDF compression as QPDF group is disabled&quot;);</span>
    +                    }
    +<span class="nc" id="L728">                    qpdfCompressionApplied = true;</span>
    +                }
    +
    +                // Check if target size reached or not in auto mode
    +<span class="nc" id="L732">                long outputFileSize = Files.size(currentFile);</span>
    +<span class="nc bnc" id="L733" title="All 4 branches missed.">                if (outputFileSize &lt;= expectedOutputSize || !autoMode) {</span>
    +<span class="nc" id="L734">                    sizeMet = true;</span>
    +                } else {
    +<span class="nc" id="L736">                    int newOptimizeLevel =</span>
    +<span class="nc" id="L737">                            incrementOptimizeLevel(</span>
    +<span class="nc" id="L738">                                    optimizeLevel, outputFileSize, expectedOutputSize);</span>
    +
    +                    // Check if we can't increase the level further
    +<span class="nc bnc" id="L741" title="All 2 branches missed.">                    if (newOptimizeLevel == optimizeLevel) {</span>
    +<span class="nc bnc" id="L742" title="All 2 branches missed.">                        if (autoMode) {</span>
    +<span class="nc" id="L743">                            log.info(</span>
    +                                    &quot;Maximum optimization level reached without meeting target size.&quot;);
    +<span class="nc" id="L745">                            sizeMet = true;</span>
    +                        }
    +                    } else {
    +                        // Reset flags for next iteration with higher optimization level
    +<span class="nc" id="L749">                        imageCompressionApplied = false;</span>
    +<span class="nc" id="L750">                        qpdfCompressionApplied = false;</span>
    +<span class="nc" id="L751">                        optimizeLevel = newOptimizeLevel;</span>
    +                    }
    +                }
    +<span class="nc" id="L754">            }</span>
    +
    +            // Use original if optimized file is somehow larger
    +<span class="nc" id="L757">            long finalFileSize = Files.size(currentFile);</span>
    +<span class="nc bnc" id="L758" title="All 2 branches missed.">            if (finalFileSize &gt;= inputFileSize) {</span>
    +<span class="nc" id="L759">                log.warn(</span>
    +                        &quot;Optimized file is larger than the original. Using the original file instead.&quot;);
    +<span class="nc" id="L761">                currentFile = originalFile;</span>
    +            }
    +
    +<span class="nc" id="L764">            String outputFilename =</span>
    +<span class="nc" id="L765">                    Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +<span class="nc" id="L766">                                    .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                            + &quot;_Optimized.pdf&quot;;
    +
    +<span class="nc" id="L769">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +<span class="nc" id="L770">                    pdfDocumentFactory.load(currentFile.toFile()), outputFilename);</span>
    +
    +        } finally {
    +            // Clean up all temporary files
    +<span class="nc bnc" id="L774" title="All 2 branches missed.">            for (Path tempFile : tempFiles) {</span>
    +                try {
    +<span class="nc" id="L776">                    Files.deleteIfExists(tempFile);</span>
    +<span class="nc" id="L777">                } catch (IOException e) {</span>
    +<span class="nc" id="L778">                    log.warn(&quot;Failed to delete temporary file: &quot; + tempFile, e);</span>
    +<span class="nc" id="L779">                }</span>
    +<span class="nc" id="L780">            }</span>
    +        }
    +    }
    +
    +    // Run QPDF compression
    +    private void applyQpdfCompression(
    +            OptimizePdfRequest request, int optimizeLevel, Path currentFile, List&lt;Path&gt; tempFiles)
    +            throws IOException {
    +
    +<span class="nc" id="L789">        long preQpdfSize = Files.size(currentFile);</span>
    +<span class="nc" id="L790">        log.info(&quot;Pre-QPDF file size: {}&quot;, GeneralUtils.formatBytes(preQpdfSize));</span>
    +
    +        // Map optimization levels to QPDF compression levels
    +        int qpdfCompressionLevel =
    +<span class="nc bnc" id="L794" title="All 2 branches missed.">                optimizeLevel &lt;= 3</span>
    +<span class="nc" id="L795">                        ? optimizeLevel * 3 // Level 1-&gt;3, 2-&gt;6, 3-&gt;9</span>
    +<span class="nc" id="L796">                        : 9; // Max compression for levels 4-9</span>
    +
    +        // Create output file for QPDF
    +<span class="nc" id="L799">        Path qpdfOutputFile = Files.createTempFile(&quot;qpdf_output_&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L800">        tempFiles.add(qpdfOutputFile);</span>
    +
    +        // Build QPDF command
    +<span class="nc" id="L803">        List&lt;String&gt; command = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L804">        command.add(&quot;qpdf&quot;);</span>
    +<span class="nc bnc" id="L805" title="All 2 branches missed.">        if (request.getNormalize()) {</span>
    +<span class="nc" id="L806">            command.add(&quot;--normalize-content=y&quot;);</span>
    +        }
    +<span class="nc bnc" id="L808" title="All 2 branches missed.">        if (request.getLinearize()) {</span>
    +<span class="nc" id="L809">            command.add(&quot;--linearize&quot;);</span>
    +        }
    +<span class="nc" id="L811">        command.add(&quot;--recompress-flate&quot;);</span>
    +<span class="nc" id="L812">        command.add(&quot;--compression-level=&quot; + qpdfCompressionLevel);</span>
    +<span class="nc" id="L813">        command.add(&quot;--compress-streams=y&quot;);</span>
    +<span class="nc" id="L814">        command.add(&quot;--object-streams=generate&quot;);</span>
    +<span class="nc" id="L815">        command.add(currentFile.toString());</span>
    +<span class="nc" id="L816">        command.add(qpdfOutputFile.toString());</span>
    +
    +<span class="nc" id="L818">        ProcessExecutorResult returnCode = null;</span>
    +        try {
    +<span class="nc" id="L820">            returnCode =</span>
    +<span class="nc" id="L821">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)</span>
    +<span class="nc" id="L822">                            .runCommandWithOutputHandling(command);</span>
    +
    +            // Update current file to the QPDF output
    +<span class="nc" id="L825">            Files.copy(qpdfOutputFile, currentFile, StandardCopyOption.REPLACE_EXISTING);</span>
    +
    +<span class="nc" id="L827">            long postQpdfSize = Files.size(currentFile);</span>
    +<span class="nc" id="L828">            double qpdfReduction = 100.0 - ((postQpdfSize * 100.0) / preQpdfSize);</span>
    +<span class="nc" id="L829">            log.info(</span>
    +                    &quot;Post-QPDF file size: {} (reduced by {}%)&quot;,
    +<span class="nc" id="L831">                    GeneralUtils.formatBytes(postQpdfSize), String.format(&quot;%.1f&quot;, qpdfReduction));</span>
    +
    +<span class="nc" id="L833">        } catch (Exception e) {</span>
    +<span class="nc bnc" id="L834" title="All 4 branches missed.">            if (returnCode != null &amp;&amp; returnCode.getRc() != 3) {</span>
    +<span class="nc" id="L835">                throw new IOException(&quot;QPDF command failed&quot;, e);</span>
    +            }
    +            // If QPDF fails, keep using the current file
    +<span class="nc" id="L838">            log.warn(&quot;QPDF compression failed, continuing with current file&quot;, e);</span>
    +<span class="nc" id="L839">        }</span>
    +<span class="nc" id="L840">    }</span>
    +
    +    // Pick optimization level based on target size
    +    private int determineOptimizeLevel(double sizeReductionRatio) {
    +<span class="nc bnc" id="L844" title="All 2 branches missed.">        if (sizeReductionRatio &gt; 0.9) return 1;</span>
    +<span class="nc bnc" id="L845" title="All 2 branches missed.">        if (sizeReductionRatio &gt; 0.8) return 2;</span>
    +<span class="nc bnc" id="L846" title="All 2 branches missed.">        if (sizeReductionRatio &gt; 0.7) return 3;</span>
    +<span class="nc bnc" id="L847" title="All 2 branches missed.">        if (sizeReductionRatio &gt; 0.6) return 4;</span>
    +<span class="nc bnc" id="L848" title="All 2 branches missed.">        if (sizeReductionRatio &gt; 0.3) return 5;</span>
    +<span class="nc bnc" id="L849" title="All 2 branches missed.">        if (sizeReductionRatio &gt; 0.2) return 6;</span>
    +<span class="nc bnc" id="L850" title="All 2 branches missed.">        if (sizeReductionRatio &gt; 0.15) return 7;</span>
    +<span class="nc bnc" id="L851" title="All 2 branches missed.">        if (sizeReductionRatio &gt; 0.1) return 8;</span>
    +<span class="nc" id="L852">        return 9;</span>
    +    }
    +
    +    // Increment optimization level if we need more compression
    +    private int incrementOptimizeLevel(int currentLevel, long currentSize, long targetSize) {
    +<span class="nc" id="L857">        double currentRatio = currentSize / (double) targetSize;</span>
    +<span class="nc" id="L858">        log.info(&quot;Current compression ratio: {}&quot;, String.format(&quot;%.2f&quot;, currentRatio));</span>
    +
    +<span class="nc bnc" id="L860" title="All 2 branches missed.">        if (currentRatio &gt; 2.0) {</span>
    +<span class="nc" id="L861">            return Math.min(9, currentLevel + 3);</span>
    +<span class="nc bnc" id="L862" title="All 2 branches missed.">        } else if (currentRatio &gt; 1.5) {</span>
    +<span class="nc" id="L863">            return Math.min(9, currentLevel + 2);</span>
    +        }
    +<span class="nc" id="L865">        return Math.min(9, currentLevel + 1);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/DecompressPdfController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/DecompressPdfController.html
    new file mode 100644
    index 000000000..a3e3bd1c0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/DecompressPdfController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DecompressPdfController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">DecompressPdfController</span></div><h1>DecompressPdfController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">209 of 209</td><td class="ctr2">0%</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="ctr1">20</td><td class="ctr2">20</td><td class="ctr1">54</td><td class="ctr2">54</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a2"><a href="DecompressPdfController.java.html#L114" class="el_method">decompressStream(COSStream)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="49" alt="49"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h0">16</td><td class="ctr2" id="i0">16</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a6"><a href="DecompressPdfController.java.html#L81" class="el_method">processObject(COSBase, Set)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="110" height="10" title="45" alt="45"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h1">9</td><td class="ctr2" id="i1">9</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="DecompressPdfController.java.html#L51" class="el_method">decompressPdf(PDFFile)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="33" alt="33"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h2">9</td><td class="ctr2" id="i2">9</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="DecompressPdfController.java.html#L69" class="el_method">processAllObjects(PDDocument)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="71" height="10" title="29" alt="29"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">7</td><td class="ctr2" id="i3">7</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="DecompressPdfController.java.html#L95" class="el_method">processDictionary(COSDictionary, Set)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="28" alt="28"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h4">6</td><td class="ctr2" id="i4">6</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a4"><a href="DecompressPdfController.java.html#L107" class="el_method">processArray(COSArray, Set)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="15" alt="15"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a1"><a href="DecompressPdfController.java.html#L40" class="el_method">DecompressPdfController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="6" alt="6"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a7"><a href="DecompressPdfController.java.html#L33" class="el_method">static {...}</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="4" alt="4"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/DecompressPdfController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/DecompressPdfController.java.html
    new file mode 100644
    index 000000000..25d120165
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/DecompressPdfController.java.html
    @@ -0,0 +1,146 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DecompressPdfController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">DecompressPdfController.java</span></div><h1>DecompressPdfController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.io.OutputStream;
    +import java.util.HashSet;
    +import java.util.Set;
    +
    +import org.apache.pdfbox.cos.*;
    +import org.apache.pdfbox.io.IOUtils;
    +import org.apache.pdfbox.pdfwriter.compress.CompressParameters;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L33">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class DecompressPdfController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L40">    public DecompressPdfController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L41">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L42">    }</span>
    +
    +    @PostMapping(value = &quot;/decompress-pdf&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Decompress PDF streams&quot;,
    +            description = &quot;Fully decompresses all PDF streams including text content&quot;)
    +    public ResponseEntity&lt;byte[]&gt; decompressPdf(@ModelAttribute PDFFile request)
    +            throws IOException {
    +
    +<span class="nc" id="L51">        MultipartFile file = request.getFileInput();</span>
    +
    +<span class="nc" id="L53">        try (PDDocument document = pdfDocumentFactory.load(file)) {</span>
    +            // Process all objects in document
    +<span class="nc" id="L55">            processAllObjects(document);</span>
    +
    +            // Save with explicit no compression
    +<span class="nc" id="L58">            ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L59">            document.save(baos, CompressParameters.NO_COMPRESSION);</span>
    +
    +<span class="nc" id="L61">            String outputFilename =</span>
    +<span class="nc" id="L62">                    file.getOriginalFilename().replaceFirst(&quot;\\.(?=[^.]+$)&quot;, &quot;_decompressed.&quot;);</span>
    +<span class="nc" id="L63">            return WebResponseUtils.bytesToWebResponse(</span>
    +<span class="nc" id="L64">                    baos.toByteArray(), outputFilename, MediaType.APPLICATION_PDF);</span>
    +        }
    +    }
    +
    +    private void processAllObjects(PDDocument document) {
    +<span class="nc" id="L69">        Set&lt;COSBase&gt; processed = new HashSet&lt;&gt;();</span>
    +<span class="nc" id="L70">        COSDocument cosDoc = document.getDocument();</span>
    +
    +        // Process all objects in the document
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">        for (COSObjectKey key : cosDoc.getXrefTable().keySet()) {</span>
    +<span class="nc" id="L74">            COSObject obj = cosDoc.getObjectFromPool(key);</span>
    +<span class="nc" id="L75">            processObject(obj, processed);</span>
    +<span class="nc" id="L76">        }</span>
    +<span class="nc" id="L77">    }</span>
    +
    +    private void processObject(COSBase obj, Set&lt;COSBase&gt; processed) {
    +        // Skip null objects or already processed objects to avoid infinite recursion
    +<span class="nc bnc" id="L81" title="All 4 branches missed.">        if (obj == null || processed.contains(obj)) return;</span>
    +<span class="nc" id="L82">        processed.add(obj);</span>
    +
    +<span class="nc bnc" id="L84" title="All 2 branches missed.">        if (obj instanceof COSObject cosObj) {</span>
    +<span class="nc" id="L85">            processObject(cosObj.getObject(), processed);</span>
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">        } else if (obj instanceof COSDictionary dict) {</span>
    +<span class="nc" id="L87">            processDictionary(dict, processed);</span>
    +<span class="nc bnc" id="L88" title="All 2 branches missed.">        } else if (obj instanceof COSArray array) {</span>
    +<span class="nc" id="L89">            processArray(array, processed);</span>
    +        }
    +<span class="nc" id="L91">    }</span>
    +
    +    private void processDictionary(COSDictionary dict, Set&lt;COSBase&gt; processed) {
    +        // Process all dictionary entries
    +<span class="nc bnc" id="L95" title="All 2 branches missed.">        for (COSName key : dict.keySet()) {</span>
    +<span class="nc" id="L96">            processObject(dict.getDictionaryObject(key), processed);</span>
    +<span class="nc" id="L97">        }</span>
    +
    +        // If this is a stream, decompress it
    +<span class="nc bnc" id="L100" title="All 2 branches missed.">        if (dict instanceof COSStream stream) {</span>
    +<span class="nc" id="L101">            decompressStream(stream);</span>
    +        }
    +<span class="nc" id="L103">    }</span>
    +
    +    private void processArray(COSArray array, Set&lt;COSBase&gt; processed) {
    +        // Process all array elements
    +<span class="nc bnc" id="L107" title="All 2 branches missed.">        for (int i = 0; i &lt; array.size(); i++) {</span>
    +<span class="nc" id="L108">            processObject(array.get(i), processed);</span>
    +        }
    +<span class="nc" id="L110">    }</span>
    +
    +    private void decompressStream(COSStream stream) {
    +        try {
    +<span class="nc" id="L114">            log.debug(&quot;Processing stream: {}&quot;, stream);</span>
    +
    +            // Only remove filter information if it exists
    +<span class="nc bnc" id="L117" title="All 2 branches missed.">            if (stream.containsKey(COSName.FILTER)</span>
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">                    || stream.containsKey(COSName.DECODE_PARMS)</span>
    +<span class="nc bnc" id="L119" title="All 2 branches missed.">                    || stream.containsKey(COSName.D)) {</span>
    +
    +                // Read the decompressed content first
    +                byte[] decompressedBytes;
    +<span class="nc" id="L123">                try (COSInputStream is = stream.createInputStream()) {</span>
    +<span class="nc" id="L124">                    decompressedBytes = IOUtils.toByteArray(is);</span>
    +                }
    +
    +                // Now remove filter information
    +<span class="nc" id="L128">                stream.removeItem(COSName.FILTER);</span>
    +<span class="nc" id="L129">                stream.removeItem(COSName.DECODE_PARMS);</span>
    +<span class="nc" id="L130">                stream.removeItem(COSName.D);</span>
    +
    +                // Write the raw content back
    +<span class="nc" id="L133">                try (OutputStream out = stream.createRawOutputStream()) {</span>
    +<span class="nc" id="L134">                    out.write(decompressedBytes);</span>
    +                }
    +
    +                // Set the Length to reflect the new stream size
    +<span class="nc" id="L138">                stream.setInt(COSName.LENGTH, decompressedBytes.length);</span>
    +            }
    +<span class="nc" id="L140">        } catch (IOException e) {</span>
    +<span class="nc" id="L141">            log.error(&quot;Error decompressing stream&quot;, e);</span>
    +            // Continue processing other streams even if this one fails
    +<span class="nc" id="L143">        }</span>
    +<span class="nc" id="L144">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImageScansController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImageScansController.html
    new file mode 100644
    index 000000000..4cd9cf507
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImageScansController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExtractImageScansController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">ExtractImageScansController</span></div><h1>ExtractImageScansController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">392 of 392</td><td class="ctr2">0%</td><td class="bar">20 of 20</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">88</td><td class="ctr2">88</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="ExtractImageScansController.java.html#L82" class="el_method">extractImageScans(ExtractImageScansRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="358" alt="358"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h0">74</td><td class="ctr2" id="i0">74</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="ExtractImageScansController.java.html#L231" class="el_method">lambda$extractImageScans$1(Path)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="ExtractImageScansController.java.html#L214" class="el_method">lambda$extractImageScans$0(Path)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="12" alt="12"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="ExtractImageScansController.java.html#L52" class="el_method">ExtractImageScansController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="ExtractImageScansController.java.html#L43" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImageScansController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImageScansController.java.html
    new file mode 100644
    index 000000000..f5e911e99
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImageScansController.java.html
    @@ -0,0 +1,239 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExtractImageScansController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">ExtractImageScansController.java</span></div><h1>ExtractImageScansController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.image.BufferedImage;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import javax.imageio.ImageIO;
    +
    +import org.apache.commons.io.FileUtils;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.rendering.PDFRenderer;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.media.Content;
    +import io.swagger.v3.oas.annotations.media.Schema;
    +import io.swagger.v3.oas.annotations.parameters.RequestBody;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.CheckProgramInstall;
    +import stirling.software.SPDF.utils.ProcessExecutor;
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L43">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class ExtractImageScansController {
    +
    +    private static final String REPLACEFIRST = &quot;[.][^.]+$&quot;;
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L52">    public ExtractImageScansController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L53">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L54">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/extract-image-scans&quot;)
    +    @Operation(
    +            summary = &quot;Extract image scans from an input file&quot;,
    +            description =
    +                    &quot;This endpoint extracts image scans from a given file based on certain&quot;
    +                            + &quot; parameters. Users can specify angle threshold, tolerance, minimum area,&quot;
    +                            + &quot; minimum contour area, and border size. Input:PDF Output:IMAGE/ZIP&quot;
    +                            + &quot; Type:SIMO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; extractImageScans(
    +            @RequestBody(
    +                            description = &quot;Form data containing file and extraction parameters&quot;,
    +                            required = true,
    +                            content =
    +                                    @Content(
    +                                            mediaType = &quot;multipart/form-data&quot;,
    +                                            schema =
    +                                                    @Schema(
    +                                                            implementation =
    +                                                                    ExtractImageScansRequest
    +                                                                            .class) // This should
    +                                            // represent
    +                                            // your form's
    +                                            // structure
    +                                            ))
    +                    ExtractImageScansRequest form)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L82">        String fileName = form.getFileInput().getOriginalFilename();</span>
    +<span class="nc" id="L83">        String extension = fileName.substring(fileName.lastIndexOf(&quot;.&quot;) + 1);</span>
    +
    +<span class="nc" id="L85">        List&lt;String&gt; images = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc" id="L87">        List&lt;Path&gt; tempImageFiles = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L88">        Path tempInputFile = null;</span>
    +<span class="nc" id="L89">        Path tempZipFile = null;</span>
    +<span class="nc" id="L90">        List&lt;Path&gt; tempDirs = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">        if (!CheckProgramInstall.isPythonAvailable()) {</span>
    +<span class="nc" id="L93">            throw new IOException(&quot;Python is not installed.&quot;);</span>
    +        }
    +
    +<span class="nc" id="L96">        String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();</span>
    +        try {
    +            // Check if input file is a PDF
    +<span class="nc bnc" id="L99" title="All 2 branches missed.">            if (&quot;pdf&quot;.equalsIgnoreCase(extension)) {</span>
    +                // Load PDF document
    +<span class="nc" id="L101">                try (PDDocument document = pdfDocumentFactory.load(form.getFileInput())) {</span>
    +<span class="nc" id="L102">                    PDFRenderer pdfRenderer = new PDFRenderer(document);</span>
    +<span class="nc" id="L103">                    pdfRenderer.setSubsamplingAllowed(true);</span>
    +<span class="nc" id="L104">                    int pageCount = document.getNumberOfPages();</span>
    +<span class="nc" id="L105">                    images = new ArrayList&lt;&gt;();</span>
    +
    +                    // Create images of all pages
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">                    for (int i = 0; i &lt; pageCount; i++) {</span>
    +                        // Create temp file to save the image
    +<span class="nc" id="L110">                        Path tempFile = Files.createTempFile(&quot;image_&quot;, &quot;.png&quot;);</span>
    +
    +                        // Render image and save as temp file
    +<span class="nc" id="L113">                        BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300);</span>
    +<span class="nc" id="L114">                        ImageIO.write(image, &quot;png&quot;, tempFile.toFile());</span>
    +
    +                        // Add temp file path to images list
    +<span class="nc" id="L117">                        images.add(tempFile.toString());</span>
    +<span class="nc" id="L118">                        tempImageFiles.add(tempFile);</span>
    +                    }
    +                }
    +            } else {
    +<span class="nc" id="L122">                tempInputFile = Files.createTempFile(&quot;input_&quot;, &quot;.&quot; + extension);</span>
    +<span class="nc" id="L123">                form.getFileInput().transferTo(tempInputFile);</span>
    +                // Add input file path to images list
    +<span class="nc" id="L125">                images.add(tempInputFile.toString());</span>
    +            }
    +
    +<span class="nc" id="L128">            List&lt;byte[]&gt; processedImageBytes = new ArrayList&lt;&gt;();</span>
    +
    +            // Process each image
    +<span class="nc bnc" id="L131" title="All 2 branches missed.">            for (int i = 0; i &lt; images.size(); i++) {</span>
    +
    +<span class="nc" id="L133">                Path tempDir = Files.createTempDirectory(&quot;openCV_output&quot;);</span>
    +<span class="nc" id="L134">                tempDirs.add(tempDir);</span>
    +<span class="nc" id="L135">                List&lt;String&gt; command =</span>
    +                        new ArrayList&lt;&gt;(
    +<span class="nc" id="L137">                                Arrays.asList(</span>
    +                                        pythonVersion,
    +                                        &quot;./scripts/split_photos.py&quot;,
    +<span class="nc" id="L140">                                        images.get(i),</span>
    +<span class="nc" id="L141">                                        tempDir.toString(),</span>
    +                                        &quot;--angle_threshold&quot;,
    +<span class="nc" id="L143">                                        String.valueOf(form.getAngleThreshold()),</span>
    +                                        &quot;--tolerance&quot;,
    +<span class="nc" id="L145">                                        String.valueOf(form.getTolerance()),</span>
    +                                        &quot;--min_area&quot;,
    +<span class="nc" id="L147">                                        String.valueOf(form.getMinArea()),</span>
    +                                        &quot;--min_contour_area&quot;,
    +<span class="nc" id="L149">                                        String.valueOf(form.getMinContourArea()),</span>
    +                                        &quot;--border_size&quot;,
    +<span class="nc" id="L151">                                        String.valueOf(form.getBorderSize())));</span>
    +
    +                // Run CLI command
    +<span class="nc" id="L154">                ProcessExecutorResult returnCode =</span>
    +<span class="nc" id="L155">                        ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)</span>
    +<span class="nc" id="L156">                                .runCommandWithOutputHandling(command);</span>
    +
    +                // Read the output photos in temp directory
    +<span class="nc" id="L159">                List&lt;Path&gt; tempOutputFiles = Files.list(tempDir).sorted().toList();</span>
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">                for (Path tempOutputFile : tempOutputFiles) {</span>
    +<span class="nc" id="L161">                    byte[] imageBytes = Files.readAllBytes(tempOutputFile);</span>
    +<span class="nc" id="L162">                    processedImageBytes.add(imageBytes);</span>
    +<span class="nc" id="L163">                }</span>
    +                // Clean up the temporary directory
    +<span class="nc" id="L165">                FileUtils.deleteDirectory(tempDir.toFile());</span>
    +            }
    +
    +            // Create zip file if multiple images
    +<span class="nc bnc" id="L169" title="All 2 branches missed.">            if (processedImageBytes.size() &gt; 1) {</span>
    +<span class="nc" id="L170">                String outputZipFilename =</span>
    +<span class="nc" id="L171">                        fileName.replaceFirst(REPLACEFIRST, &quot;&quot;) + &quot;_processed.zip&quot;;</span>
    +<span class="nc" id="L172">                tempZipFile = Files.createTempFile(&quot;output_&quot;, &quot;.zip&quot;);</span>
    +
    +<span class="nc" id="L174">                try (ZipOutputStream zipOut =</span>
    +<span class="nc" id="L175">                        new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) {</span>
    +                    // Add processed images to the zip
    +<span class="nc bnc" id="L177" title="All 2 branches missed.">                    for (int i = 0; i &lt; processedImageBytes.size(); i++) {</span>
    +<span class="nc" id="L178">                        ZipEntry entry =</span>
    +                                new ZipEntry(
    +<span class="nc" id="L180">                                        fileName.replaceFirst(REPLACEFIRST, &quot;&quot;)</span>
    +                                                + &quot;_&quot;
    +                                                + (i + 1)
    +                                                + &quot;.png&quot;);
    +<span class="nc" id="L184">                        zipOut.putNextEntry(entry);</span>
    +<span class="nc" id="L185">                        zipOut.write(processedImageBytes.get(i));</span>
    +<span class="nc" id="L186">                        zipOut.closeEntry();</span>
    +                    }
    +                }
    +
    +<span class="nc" id="L190">                byte[] zipBytes = Files.readAllBytes(tempZipFile);</span>
    +
    +                // Clean up the temporary zip file
    +<span class="nc" id="L193">                Files.deleteIfExists(tempZipFile);</span>
    +
    +<span class="nc" id="L195">                return WebResponseUtils.bytesToWebResponse(</span>
    +                        zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM);
    +            }
    +<span class="nc bnc" id="L198" title="All 2 branches missed.">            if (processedImageBytes.size() == 0) {</span>
    +<span class="nc" id="L199">                throw new IllegalArgumentException(&quot;No images detected&quot;);</span>
    +            } else {
    +
    +                // Return the processed image as a response
    +<span class="nc" id="L203">                byte[] imageBytes = processedImageBytes.get(0);</span>
    +<span class="nc" id="L204">                return WebResponseUtils.bytesToWebResponse(</span>
    +                        imageBytes,
    +<span class="nc" id="L206">                        fileName.replaceFirst(REPLACEFIRST, &quot;&quot;) + &quot;.png&quot;,</span>
    +                        MediaType.IMAGE_PNG);
    +            }
    +        } finally {
    +            // Cleanup logic for all temporary files and directories
    +<span class="nc" id="L211">            tempImageFiles.forEach(</span>
    +                    path -&gt; {
    +                        try {
    +<span class="nc" id="L214">                            Files.deleteIfExists(path);</span>
    +<span class="nc" id="L215">                        } catch (IOException e) {</span>
    +<span class="nc" id="L216">                            log.error(&quot;Failed to delete temporary image file: &quot; + path, e);</span>
    +<span class="nc" id="L217">                        }</span>
    +<span class="nc" id="L218">                    });</span>
    +
    +<span class="nc bnc" id="L220" title="All 4 branches missed.">            if (tempZipFile != null &amp;&amp; Files.exists(tempZipFile)) {</span>
    +                try {
    +<span class="nc" id="L222">                    Files.deleteIfExists(tempZipFile);</span>
    +<span class="nc" id="L223">                } catch (IOException e) {</span>
    +<span class="nc" id="L224">                    log.error(&quot;Failed to delete temporary zip file: &quot; + tempZipFile, e);</span>
    +<span class="nc" id="L225">                }</span>
    +            }
    +
    +<span class="nc" id="L228">            tempDirs.forEach(</span>
    +                    dir -&gt; {
    +                        try {
    +<span class="nc" id="L231">                            FileUtils.deleteDirectory(dir.toFile());</span>
    +<span class="nc" id="L232">                        } catch (IOException e) {</span>
    +<span class="nc" id="L233">                            log.error(&quot;Failed to delete temporary directory: &quot; + dir, e);</span>
    +<span class="nc" id="L234">                        }</span>
    +<span class="nc" id="L235">                    });</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImagesController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImagesController.html
    new file mode 100644
    index 000000000..1f07fa91a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImagesController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExtractImagesController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">ExtractImagesController</span></div><h1>ExtractImagesController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">382 of 382</td><td class="ctr2">0%</td><td class="bar">32 of 32</td><td class="ctr2">0%</td><td class="ctr1">24</td><td class="ctr2">24</td><td class="ctr1">92</td><td class="ctr2">92</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a1"><a href="ExtractImagesController.java.html#L69" class="el_method">extractImages(PDFExtractImagesRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="135" alt="135"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h0">32</td><td class="ctr2" id="i0">32</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="ExtractImagesController.java.html#L177" class="el_method">extractImagesFromPage(PDPage, String, String, int, Set, ZipOutputStream, boolean)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="104" height="10" title="118" alt="118"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h1">32</td><td class="ctr2" id="i1">32</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="ExtractImagesController.java.html#L220" class="el_method">convertToRGB(RenderedImage, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="68" alt="68"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h2">13</td><td class="ctr2" id="i2">13</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="ExtractImagesController.java.html#L105" class="el_method">lambda$extractImages$0(PDDocument, PDPage, String, String, Set, ZipOutputStream, boolean)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="27" alt="27"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">8</td><td class="ctr2" id="i3">8</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a6"><a href="ExtractImagesController.java.html#L161" class="el_method">shouldUseMultithreading(MultipartFile, PDDocument)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="20" alt="20"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h4">3</td><td class="ctr2" id="i4">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a2"><a href="ExtractImagesController.java.html#L56" class="el_method">ExtractImagesController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a5"><a href="ExtractImagesController.java.html#L194" class="el_method">lambda$extractImagesFromPage$1(byte[], byte[])</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a7"><a href="ExtractImagesController.java.html#L49" class="el_method">static {...}</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImagesController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImagesController.java.html
    new file mode 100644
    index 000000000..99e31cd7e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/ExtractImagesController.java.html
    @@ -0,0 +1,240 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ExtractImagesController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">ExtractImagesController.java</span></div><h1>ExtractImagesController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.*;
    +import java.awt.image.BufferedImage;
    +import java.awt.image.RenderedImage;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.security.MessageDigest;
    +import java.security.NoSuchAlgorithmException;
    +import java.util.Arrays;
    +import java.util.HashSet;
    +import java.util.Set;
    +import java.util.concurrent.ExecutionException;
    +import java.util.concurrent.ExecutorService;
    +import java.util.concurrent.Executors;
    +import java.util.concurrent.Future;
    +import java.util.zip.Deflater;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import javax.imageio.ImageIO;
    +
    +import org.apache.pdfbox.cos.COSName;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.PDFExtractImagesRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.ImageProcessingUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L49">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class ExtractImagesController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L56">    public ExtractImagesController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L57">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L58">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/extract-images&quot;)
    +    @Operation(
    +            summary = &quot;Extract images from a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint extracts images from a given PDF file and returns them in a zip&quot;
    +                            + &quot; file. Users can specify the output image format. Input:PDF&quot;
    +                            + &quot; Output:IMAGE/ZIP Type:SIMO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; extractImages(@ModelAttribute PDFExtractImagesRequest request)
    +            throws IOException, InterruptedException, ExecutionException {
    +<span class="nc" id="L69">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L70">        String format = request.getFormat();</span>
    +<span class="nc" id="L71">        boolean allowDuplicates = request.isAllowDuplicates();</span>
    +<span class="nc" id="L72">        PDDocument document = pdfDocumentFactory.load(file);</span>
    +
    +        // Determine if multithreading should be used based on PDF size or number of pages
    +<span class="nc" id="L75">        boolean useMultithreading = shouldUseMultithreading(file, document);</span>
    +
    +        // Create ByteArrayOutputStream to write zip file to byte array
    +<span class="nc" id="L78">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +
    +        // Create ZipOutputStream to create zip file
    +<span class="nc" id="L81">        ZipOutputStream zos = new ZipOutputStream(baos);</span>
    +
    +        // Set compression level
    +<span class="nc" id="L84">        zos.setLevel(Deflater.BEST_COMPRESSION);</span>
    +
    +<span class="nc" id="L86">        String filename =</span>
    +<span class="nc" id="L87">                Filenames.toSimpleFileName(file.getOriginalFilename())</span>
    +<span class="nc" id="L88">                        .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L89">        Set&lt;byte[]&gt; processedImages = new HashSet&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">        if (useMultithreading) {</span>
    +            // Executor service to handle multithreading
    +            ExecutorService executor =
    +<span class="nc" id="L94">                    Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());</span>
    +<span class="nc" id="L95">            Set&lt;Future&lt;Void&gt;&gt; futures = new HashSet&lt;&gt;();</span>
    +
    +            // Iterate over each page
    +<span class="nc bnc" id="L98" title="All 2 branches missed.">            for (int pgNum = 0; pgNum &lt; document.getPages().getCount(); pgNum++) {</span>
    +<span class="nc" id="L99">                PDPage page = document.getPage(pgNum);</span>
    +<span class="nc" id="L100">                Future&lt;Void&gt; future =</span>
    +<span class="nc" id="L101">                        executor.submit(</span>
    +                                () -&gt; {
    +                                    // Use the page number directly from the iterator, so no need to
    +                                    // calculate manually
    +<span class="nc" id="L105">                                    int pageNum = document.getPages().indexOf(page) + 1;</span>
    +
    +                                    try {
    +                                        // Call the image extraction method for each page
    +<span class="nc" id="L109">                                        extractImagesFromPage(</span>
    +                                                page,
    +                                                format,
    +                                                filename,
    +                                                pageNum,
    +                                                processedImages,
    +                                                zos,
    +                                                allowDuplicates);
    +<span class="nc" id="L117">                                    } catch (IOException e) {</span>
    +                                        // Log the error and continue processing other pages
    +<span class="nc" id="L119">                                        log.error(</span>
    +                                                &quot;Error extracting images from page {}: {}&quot;,
    +<span class="nc" id="L121">                                                pageNum,</span>
    +<span class="nc" id="L122">                                                e.getMessage());</span>
    +<span class="nc" id="L123">                                    }</span>
    +
    +<span class="nc" id="L125">                                    return null; // Callable requires a return type</span>
    +                                });
    +
    +                // Add the Future object to the list to track completion
    +<span class="nc" id="L129">                futures.add(future);</span>
    +            }
    +
    +            // Wait for all tasks to complete
    +<span class="nc bnc" id="L133" title="All 2 branches missed.">            for (Future&lt;Void&gt; future : futures) {</span>
    +<span class="nc" id="L134">                future.get();</span>
    +<span class="nc" id="L135">            }</span>
    +
    +            // Close executor service
    +<span class="nc" id="L138">            executor.shutdown();</span>
    +<span class="nc" id="L139">        } else {</span>
    +            // Single-threaded extraction
    +<span class="nc bnc" id="L141" title="All 2 branches missed.">            for (int pgNum = 0; pgNum &lt; document.getPages().getCount(); pgNum++) {</span>
    +<span class="nc" id="L142">                PDPage page = document.getPage(pgNum);</span>
    +<span class="nc" id="L143">                extractImagesFromPage(</span>
    +                        page, format, filename, pgNum + 1, processedImages, zos, allowDuplicates);
    +            }
    +        }
    +
    +        // Close PDDocument and ZipOutputStream
    +<span class="nc" id="L149">        document.close();</span>
    +<span class="nc" id="L150">        zos.close();</span>
    +
    +        // Create ByteArrayResource from byte array
    +<span class="nc" id="L153">        byte[] zipContents = baos.toByteArray();</span>
    +
    +<span class="nc" id="L155">        return WebResponseUtils.boasToWebResponse(</span>
    +                baos, filename + &quot;_extracted-images.zip&quot;, MediaType.APPLICATION_OCTET_STREAM);
    +    }
    +
    +    private boolean shouldUseMultithreading(MultipartFile file, PDDocument document) {
    +        // Criteria: Use multithreading if file size &gt; 10MB or number of pages &gt; 20
    +<span class="nc" id="L161">        long fileSizeInMB = file.getSize() / (1024 * 1024);</span>
    +<span class="nc" id="L162">        int numberOfPages = document.getPages().getCount();</span>
    +<span class="nc bnc" id="L163" title="All 4 branches missed.">        return fileSizeInMB &gt; 10 || numberOfPages &gt; 20;</span>
    +    }
    +
    +    private void extractImagesFromPage(
    +            PDPage page,
    +            String format,
    +            String filename,
    +            int pageNum,
    +            Set&lt;byte[]&gt; processedImages,
    +            ZipOutputStream zos,
    +            boolean allowDuplicates)
    +            throws IOException {
    +        MessageDigest md;
    +        try {
    +<span class="nc" id="L177">            md = MessageDigest.getInstance(&quot;MD5&quot;);</span>
    +<span class="nc" id="L178">        } catch (NoSuchAlgorithmException e) {</span>
    +<span class="nc" id="L179">            log.error(&quot;MD5 algorithm not available for extractImages hash.&quot;, e);</span>
    +<span class="nc" id="L180">            return;</span>
    +<span class="nc" id="L181">        }</span>
    +<span class="nc bnc" id="L182" title="All 4 branches missed.">        if (page.getResources() == null || page.getResources().getXObjectNames() == null) {</span>
    +<span class="nc" id="L183">            return;</span>
    +        }
    +<span class="nc" id="L185">        int count = 1;</span>
    +<span class="nc bnc" id="L186" title="All 2 branches missed.">        for (COSName name : page.getResources().getXObjectNames()) {</span>
    +<span class="nc bnc" id="L187" title="All 2 branches missed.">            if (page.getResources().isImageXObject(name)) {</span>
    +<span class="nc" id="L188">                PDImageXObject image = (PDImageXObject) page.getResources().getXObject(name);</span>
    +<span class="nc bnc" id="L189" title="All 2 branches missed.">                if (!allowDuplicates) {</span>
    +<span class="nc" id="L190">                    byte[] data = ImageProcessingUtils.getImageData(image.getImage());</span>
    +<span class="nc" id="L191">                    byte[] imageHash = md.digest(data);</span>
    +<span class="nc" id="L192">                    synchronized (processedImages) {</span>
    +<span class="nc" id="L193">                        if (processedImages.stream()</span>
    +<span class="nc bnc" id="L194" title="All 2 branches missed.">                                .anyMatch(hash -&gt; Arrays.equals(hash, imageHash))) {</span>
    +<span class="nc" id="L195">                            continue; // Skip already processed images</span>
    +                        }
    +<span class="nc" id="L197">                        processedImages.add(imageHash);</span>
    +<span class="nc" id="L198">                    }</span>
    +                }
    +
    +<span class="nc" id="L201">                RenderedImage renderedImage = image.getImage();</span>
    +
    +                // Convert to standard RGB colorspace if needed
    +<span class="nc" id="L204">                BufferedImage bufferedImage = convertToRGB(renderedImage, format);</span>
    +
    +                // Write image to zip file
    +<span class="nc" id="L207">                String imageName = filename + &quot;_page_&quot; + pageNum + &quot;_&quot; + count++ + &quot;.&quot; + format;</span>
    +<span class="nc" id="L208">                synchronized (zos) {</span>
    +<span class="nc" id="L209">                    zos.putNextEntry(new ZipEntry(imageName));</span>
    +<span class="nc" id="L210">                    ByteArrayOutputStream imageBaos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L211">                    ImageIO.write(bufferedImage, format, imageBaos);</span>
    +<span class="nc" id="L212">                    zos.write(imageBaos.toByteArray());</span>
    +<span class="nc" id="L213">                    zos.closeEntry();</span>
    +<span class="nc" id="L214">                }</span>
    +            }
    +<span class="nc" id="L216">        }</span>
    +<span class="nc" id="L217">    }</span>
    +
    +    private BufferedImage convertToRGB(RenderedImage renderedImage, String format) {
    +<span class="nc" id="L220">        int width = renderedImage.getWidth();</span>
    +<span class="nc" id="L221">        int height = renderedImage.getHeight();</span>
    +        BufferedImage rgbImage;
    +
    +<span class="nc bnc" id="L224" title="All 2 branches missed.">        if (&quot;png&quot;.equalsIgnoreCase(format)) {</span>
    +<span class="nc" id="L225">            rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);</span>
    +<span class="nc bnc" id="L226" title="All 4 branches missed.">        } else if (&quot;jpeg&quot;.equalsIgnoreCase(format) || &quot;jpg&quot;.equalsIgnoreCase(format)) {</span>
    +<span class="nc" id="L227">            rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);</span>
    +<span class="nc bnc" id="L228" title="All 2 branches missed.">        } else if (&quot;gif&quot;.equalsIgnoreCase(format)) {</span>
    +<span class="nc" id="L229">            rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_INDEXED);</span>
    +        } else {
    +<span class="nc" id="L231">            rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);</span>
    +        }
    +
    +<span class="nc" id="L234">        Graphics2D g = rgbImage.createGraphics();</span>
    +<span class="nc" id="L235">        g.drawImage((Image) renderedImage, 0, 0, null);</span>
    +<span class="nc" id="L236">        g.dispose();</span>
    +<span class="nc" id="L237">        return rgbImage;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/FakeScanControllerWIP.html b/testResult/html/stirling.software.SPDF.controller.api.misc/FakeScanControllerWIP.html
    new file mode 100644
    index 000000000..6fe19ca20
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/FakeScanControllerWIP.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FakeScanControllerWIP</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">FakeScanControllerWIP</span></div><h1>FakeScanControllerWIP</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">886 of 886</td><td class="ctr2">0%</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="ctr1">26</td><td class="ctr2">26</td><td class="ctr1">139</td><td class="ctr2">139</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a8"><a href="FakeScanControllerWIP.java.html#L136" class="el_method">softenEdges(BufferedImage, int)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="182" alt="182"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h3">19</td><td class="ctr2" id="i3">19</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="FakeScanControllerWIP.java.html#L199" class="el_method">addDustAndHairs(BufferedImage, float)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="109" height="10" title="166" alt="166"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h0">28</td><td class="ctr2" id="i0">28</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="FakeScanControllerWIP.java.html#L270" class="el_method">linearStretch(BufferedImage)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="109" height="10" title="166" alt="166"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h1">23</td><td class="ctr2" id="i1">23</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="FakeScanControllerWIP.java.html#L242" class="el_method">addGaussianNoise(BufferedImage, double)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="114" alt="114"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h4">18</td><td class="ctr2" id="i4">18</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="FakeScanControllerWIP.java.html#L109" class="el_method">applyGaussianBlur(BufferedImage, double)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="73" height="10" title="112" alt="112"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h5">16</td><td class="ctr2" id="i5">16</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="FakeScanControllerWIP.java.html#L50" class="el_method">fakeScan(PDFFile)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="83" alt="83"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h2">20</td><td class="ctr2" id="i2">20</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a7"><a href="FakeScanControllerWIP.java.html#L99" class="el_method">rotate(BufferedImage, double)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="31" alt="31"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">7</td><td class="ctr2" id="i6">7</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a6"><a href="FakeScanControllerWIP.java.html#L87" class="el_method">processImage(BufferedImage)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="29" alt="29"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">7</td><td class="ctr2" id="i7">7</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a4"><a href="FakeScanControllerWIP.java.html#L40" class="el_method">FakeScanControllerWIP()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="3" alt="3"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/FakeScanControllerWIP.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/FakeScanControllerWIP.java.html
    new file mode 100644
    index 000000000..8b19be10b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/FakeScanControllerWIP.java.html
    @@ -0,0 +1,312 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FakeScanControllerWIP.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">FakeScanControllerWIP.java</span></div><h1>FakeScanControllerWIP.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.*;
    +import java.awt.geom.AffineTransform;
    +import java.awt.geom.Ellipse2D;
    +import java.awt.geom.Path2D;
    +import java.awt.image.*;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.security.SecureRandom;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Random;
    +
    +import org.apache.pdfbox.Loader;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.apache.pdfbox.rendering.ImageType;
    +import org.apache.pdfbox.rendering.PDFRenderer;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Hidden;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.utils.PdfUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +<span class="nc" id="L40">public class FakeScanControllerWIP {</span>
    +
    +    // TODO finish
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/fake-scan&quot;)
    +    @Hidden
    +    @Operation(
    +            summary = &quot;Repair a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint repairs a given PDF file by running qpdf command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.&quot;)
    +    public ResponseEntity&lt;byte[]&gt; fakeScan(@ModelAttribute PDFFile request) throws IOException {
    +<span class="nc" id="L50">        MultipartFile inputFile = request.getFileInput();</span>
    +
    +        // Load the PDF document
    +<span class="nc" id="L53">        PDDocument document = Loader.loadPDF(inputFile.getBytes());</span>
    +<span class="nc" id="L54">        PDFRenderer renderer = new PDFRenderer(document);</span>
    +<span class="nc" id="L55">        List&lt;BufferedImage&gt; images = new ArrayList&lt;&gt;();</span>
    +        // Convert each page to an image
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">        for (int i = 0; i &lt; document.getNumberOfPages(); i++) {</span>
    +<span class="nc" id="L58">            BufferedImage image = renderer.renderImageWithDPI(i, 150, ImageType.GRAY);</span>
    +<span class="nc" id="L59">            images.add(processImage(image));</span>
    +        }
    +<span class="nc" id="L61">        document.close();</span>
    +
    +        // Create a new PDF document with the processed images
    +<span class="nc" id="L64">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L65">        PDDocument newDocument = new PDDocument();</span>
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">        for (BufferedImage img : images) {</span>
    +            // PDPageContentStream contentStream = new PDPageContentStream(newDocument, new
    +            // PDPage());
    +<span class="nc" id="L69">            PDImageXObject pdImage = JPEGFactory.createFromImage(newDocument, img);</span>
    +<span class="nc" id="L70">            PdfUtils.addImageToDocument(newDocument, pdImage, &quot;maintainAspectRatio&quot;, false);</span>
    +<span class="nc" id="L71">        }</span>
    +
    +<span class="nc" id="L73">        newDocument.save(baos);</span>
    +<span class="nc" id="L74">        newDocument.close();</span>
    +
    +        // Return the optimized PDF as a response
    +<span class="nc" id="L77">        String outputFilename =</span>
    +<span class="nc" id="L78">                Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +<span class="nc" id="L79">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_scanned.pdf&quot;;
    +<span class="nc" id="L81">        return WebResponseUtils.boasToWebResponse(baos, outputFilename);</span>
    +    }
    +
    +    public BufferedImage processImage(BufferedImage image) {
    +        // Rotation
    +
    +<span class="nc" id="L87">        image = softenEdges(image, 50);</span>
    +<span class="nc" id="L88">        image = rotate(image, 1);</span>
    +
    +<span class="nc" id="L90">        image = applyGaussianBlur(image, 0.5);</span>
    +<span class="nc" id="L91">        addGaussianNoise(image, 0.5);</span>
    +<span class="nc" id="L92">        image = linearStretch(image);</span>
    +<span class="nc" id="L93">        addDustAndHairs(image, 3);</span>
    +<span class="nc" id="L94">        return image;</span>
    +    }
    +
    +    private BufferedImage rotate(BufferedImage image, double rotation) {
    +
    +<span class="nc" id="L99">        double rotationRequired = Math.toRadians(rotation);</span>
    +<span class="nc" id="L100">        double locationX = (double) image.getWidth() / 2;</span>
    +<span class="nc" id="L101">        double locationY = (double) image.getHeight() / 2;</span>
    +<span class="nc" id="L102">        AffineTransform tx =</span>
    +<span class="nc" id="L103">                AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);</span>
    +<span class="nc" id="L104">        AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);</span>
    +<span class="nc" id="L105">        return op.filter(image, null);</span>
    +    }
    +
    +    private BufferedImage applyGaussianBlur(BufferedImage image, double sigma) {
    +<span class="nc" id="L109">        int radius = 3; // Fixed radius size for simplicity</span>
    +
    +<span class="nc" id="L111">        int size = 2 * radius + 1;</span>
    +<span class="nc" id="L112">        float[] data = new float[size * size];</span>
    +<span class="nc" id="L113">        double sum = 0.0;</span>
    +
    +<span class="nc bnc" id="L115" title="All 2 branches missed.">        for (int i = -radius; i &lt;= radius; i++) {</span>
    +<span class="nc bnc" id="L116" title="All 2 branches missed.">            for (int j = -radius; j &lt;= radius; j++) {</span>
    +<span class="nc" id="L117">                double xDistance = (double) i * i;</span>
    +<span class="nc" id="L118">                double yDistance = (double) j * j;</span>
    +<span class="nc" id="L119">                double g = Math.exp(-(xDistance + yDistance) / (2 * sigma * sigma));</span>
    +<span class="nc" id="L120">                data[(i + radius) * size + j + radius] = (float) g;</span>
    +<span class="nc" id="L121">                sum += g;</span>
    +            }
    +        }
    +
    +        // Normalize the kernel
    +<span class="nc bnc" id="L126" title="All 2 branches missed.">        for (int i = 0; i &lt; data.length; i++) {</span>
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">            if (sum != 0) data[i] /= sum;</span>
    +        }
    +
    +<span class="nc" id="L130">        Kernel kernel = new Kernel(size, size, data);</span>
    +<span class="nc" id="L131">        BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);</span>
    +<span class="nc" id="L132">        return op.filter(image, null);</span>
    +    }
    +
    +    public BufferedImage softenEdges(BufferedImage image, int featherRadius) {
    +<span class="nc" id="L136">        int width = image.getWidth();</span>
    +<span class="nc" id="L137">        int height = image.getHeight();</span>
    +<span class="nc" id="L138">        BufferedImage output = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);</span>
    +
    +<span class="nc" id="L140">        Graphics2D g2 = output.createGraphics();</span>
    +<span class="nc" id="L141">        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);</span>
    +<span class="nc" id="L142">        g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);</span>
    +<span class="nc" id="L143">        g2.setRenderingHint(</span>
    +                RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    +
    +<span class="nc" id="L146">        g2.drawImage(image, 0, 0, null);</span>
    +<span class="nc" id="L147">        g2.setComposite(AlphaComposite.DstIn);</span>
    +
    +        // Top edge
    +<span class="nc" id="L150">        g2.setPaint(</span>
    +                new GradientPaint(
    +                        0,
    +                        0,
    +                        new Color(0, 0, 0, 1f),
    +                        0,
    +                        featherRadius * 2f,
    +                        new Color(0, 0, 0, 0f)));
    +<span class="nc" id="L158">        g2.fillRect(0, 0, width, featherRadius);</span>
    +
    +        // Bottom edge
    +<span class="nc" id="L161">        g2.setPaint(</span>
    +                new GradientPaint(
    +                        0,
    +                        height - featherRadius * 2f,
    +                        new Color(0, 0, 0, 0f),
    +                        0,
    +                        height,
    +                        new Color(0, 0, 0, 1f)));
    +<span class="nc" id="L169">        g2.fillRect(0, height - featherRadius, width, featherRadius);</span>
    +
    +        // Left edge
    +<span class="nc" id="L172">        g2.setPaint(</span>
    +                new GradientPaint(
    +                        0,
    +                        0,
    +                        new Color(0, 0, 0, 1f),
    +                        featherRadius * 2f,
    +                        0,
    +                        new Color(0, 0, 0, 0f)));
    +<span class="nc" id="L180">        g2.fillRect(0, 0, featherRadius, height);</span>
    +
    +        // Right edge
    +<span class="nc" id="L183">        g2.setPaint(</span>
    +                new GradientPaint(
    +                        width - featherRadius * 2f,
    +                        0,
    +                        new Color(0, 0, 0, 0f),
    +                        width,
    +                        0,
    +                        new Color(0, 0, 0, 1f)));
    +<span class="nc" id="L191">        g2.fillRect(width - featherRadius, 0, featherRadius, height);</span>
    +
    +<span class="nc" id="L193">        g2.dispose();</span>
    +
    +<span class="nc" id="L195">        return output;</span>
    +    }
    +
    +    private void addDustAndHairs(BufferedImage image, float intensity) {
    +<span class="nc" id="L199">        int width = image.getWidth();</span>
    +<span class="nc" id="L200">        int height = image.getHeight();</span>
    +<span class="nc" id="L201">        Graphics2D g2d = image.createGraphics();</span>
    +<span class="nc" id="L202">        Random random = new SecureRandom();</span>
    +
    +        // Set rendering hints for better quality
    +<span class="nc" id="L205">        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);</span>
    +
    +        // Calculate the number of artifacts based on intensity
    +<span class="nc" id="L208">        int numSpots = (int) (intensity * 10);</span>
    +<span class="nc" id="L209">        int numHairs = (int) (intensity * 20);</span>
    +
    +        // Add spots with more variable sizes
    +<span class="nc" id="L212">        g2d.setColor(new Color(100, 100, 100, 50)); // Semi-transparent gray</span>
    +<span class="nc bnc" id="L213" title="All 2 branches missed.">        for (int i = 0; i &lt; numSpots; i++) {</span>
    +<span class="nc" id="L214">            int x = random.nextInt(width);</span>
    +<span class="nc" id="L215">            int y = random.nextInt(height);</span>
    +<span class="nc" id="L216">            int ovalSize = 1 + random.nextInt(3); // Base size + variable component</span>
    +<span class="nc bnc" id="L217" title="All 2 branches missed.">            if (random.nextFloat() &gt; 0.9) {</span>
    +                // 10% chance to get a larger spot
    +<span class="nc" id="L219">                ovalSize += random.nextInt(3);</span>
    +            }
    +<span class="nc" id="L221">            g2d.fill(new Ellipse2D.Double(x, y, ovalSize, ovalSize));</span>
    +        }
    +
    +        // Add hairs
    +<span class="nc" id="L225">        g2d.setStroke(new BasicStroke(0.5f)); // Thin stroke for hairs</span>
    +<span class="nc" id="L226">        g2d.setColor(new Color(80, 80, 80, 40)); // Slightly lighter and more transparent</span>
    +<span class="nc bnc" id="L227" title="All 2 branches missed.">        for (int i = 0; i &lt; numHairs; i++) {</span>
    +<span class="nc" id="L228">            int x1 = random.nextInt(width);</span>
    +<span class="nc" id="L229">            int y1 = random.nextInt(height);</span>
    +<span class="nc" id="L230">            int x2 = x1 + random.nextInt(20) - 10; // Random length and direction</span>
    +<span class="nc" id="L231">            int y2 = y1 + random.nextInt(20) - 10;</span>
    +<span class="nc" id="L232">            Path2D.Double hair = new Path2D.Double();</span>
    +<span class="nc" id="L233">            hair.moveTo(x1, y1);</span>
    +<span class="nc" id="L234">            hair.curveTo(x1, y1, (double) (x1 + x2) / 2, (double) (y1 + y2) / 2, x2, y2);</span>
    +<span class="nc" id="L235">            g2d.draw(hair);</span>
    +        }
    +
    +<span class="nc" id="L238">        g2d.dispose();</span>
    +<span class="nc" id="L239">    }</span>
    +
    +    private void addGaussianNoise(BufferedImage image, double strength) {
    +<span class="nc" id="L242">        Random rand = new SecureRandom();</span>
    +<span class="nc" id="L243">        int width = image.getWidth();</span>
    +<span class="nc" id="L244">        int height = image.getHeight();</span>
    +
    +<span class="nc bnc" id="L246" title="All 2 branches missed.">        for (int i = 0; i &lt; width; i++) {</span>
    +<span class="nc bnc" id="L247" title="All 2 branches missed.">            for (int j = 0; j &lt; height; j++) {</span>
    +<span class="nc" id="L248">                int rgba = image.getRGB(i, j);</span>
    +<span class="nc" id="L249">                int alpha = (rgba &gt;&gt; 24) &amp; 0xff;</span>
    +<span class="nc" id="L250">                int red = (rgba &gt;&gt; 16) &amp; 0xff;</span>
    +<span class="nc" id="L251">                int green = (rgba &gt;&gt; 8) &amp; 0xff;</span>
    +<span class="nc" id="L252">                int blue = rgba &amp; 0xff;</span>
    +
    +                // Apply Gaussian noise
    +<span class="nc" id="L255">                red = (int) (red + rand.nextGaussian() * strength);</span>
    +<span class="nc" id="L256">                green = (int) (green + rand.nextGaussian() * strength);</span>
    +<span class="nc" id="L257">                blue = (int) (blue + rand.nextGaussian() * strength);</span>
    +
    +                // Clamping values to the 0-255 range
    +<span class="nc" id="L260">                red = Math.min(Math.max(0, red), 255);</span>
    +<span class="nc" id="L261">                green = Math.min(Math.max(0, green), 255);</span>
    +<span class="nc" id="L262">                blue = Math.min(Math.max(0, blue), 255);</span>
    +
    +<span class="nc" id="L264">                image.setRGB(i, j, (alpha &lt;&lt; 24) | (red &lt;&lt; 16) | (green &lt;&lt; 8) | blue);</span>
    +            }
    +        }
    +<span class="nc" id="L267">    }</span>
    +
    +    public BufferedImage linearStretch(BufferedImage image) {
    +<span class="nc" id="L270">        int width = image.getWidth();</span>
    +<span class="nc" id="L271">        int height = image.getHeight();</span>
    +<span class="nc" id="L272">        int min = 255;</span>
    +<span class="nc" id="L273">        int max = 0;</span>
    +
    +        // First pass: find the min and max grayscale values
    +<span class="nc bnc" id="L276" title="All 2 branches missed.">        for (int y = 0; y &lt; height; y++) {</span>
    +<span class="nc bnc" id="L277" title="All 2 branches missed.">            for (int x = 0; x &lt; width; x++) {</span>
    +<span class="nc" id="L278">                int rgb = image.getRGB(x, y);</span>
    +<span class="nc" id="L279">                int gray =</span>
    +                        (int)
    +                                (((rgb &gt;&gt; 16) &amp; 0xff) * 0.299
    +                                        + ((rgb &gt;&gt; 8) &amp; 0xff) * 0.587
    +                                        + (rgb &amp; 0xff) * 0.114); // Convert to grayscale
    +<span class="nc bnc" id="L284" title="All 2 branches missed.">                if (gray &lt; min) min = gray;</span>
    +<span class="nc bnc" id="L285" title="All 2 branches missed.">                if (gray &gt; max) max = gray;</span>
    +            }
    +        }
    +
    +        // Second pass: stretch the histogram
    +<span class="nc bnc" id="L290" title="All 2 branches missed.">        for (int y = 0; y &lt; height; y++) {</span>
    +<span class="nc bnc" id="L291" title="All 2 branches missed.">            for (int x = 0; x &lt; width; x++) {</span>
    +<span class="nc" id="L292">                int rgb = image.getRGB(x, y);</span>
    +<span class="nc" id="L293">                int alpha = (rgb &gt;&gt; 24) &amp; 0xff;</span>
    +<span class="nc" id="L294">                int red = (rgb &gt;&gt; 16) &amp; 0xff;</span>
    +<span class="nc" id="L295">                int green = (rgb &gt;&gt; 8) &amp; 0xff;</span>
    +<span class="nc" id="L296">                int blue = rgb &amp; 0xff;</span>
    +
    +                // Apply linear stretch to each channel
    +<span class="nc" id="L299">                red = (int) (((red - min) / (float) (max - min)) * 255);</span>
    +<span class="nc" id="L300">                green = (int) (((green - min) / (float) (max - min)) * 255);</span>
    +<span class="nc" id="L301">                blue = (int) (((blue - min) / (float) (max - min)) * 255);</span>
    +
    +                // Set new RGB value maintaining the alpha channel
    +<span class="nc" id="L304">                rgb = (alpha &lt;&lt; 24) | (red &lt;&lt; 16) | (green &lt;&lt; 8) | blue;</span>
    +<span class="nc" id="L305">                image.setRGB(x, y, rgb);</span>
    +            }
    +        }
    +
    +<span class="nc" id="L309">        return image;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/FlattenController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/FlattenController.html
    new file mode 100644
    index 000000000..655dd7dca
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/FlattenController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FlattenController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">FlattenController</span></div><h1>FlattenController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">115 of 115</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">32</td><td class="ctr2">32</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="FlattenController.java.html#L52" class="el_method">flatten(FlattenRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="105" alt="105"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">28</td><td class="ctr2" id="i0">28</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="FlattenController.java.html#L41" class="el_method">FlattenController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="FlattenController.java.html#L34" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/FlattenController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/FlattenController.java.html
    new file mode 100644
    index 000000000..39e01fe15
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/FlattenController.java.html
    @@ -0,0 +1,94 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FlattenController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">FlattenController.java</span></div><h1>FlattenController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.image.BufferedImage;
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
    +import org.apache.pdfbox.rendering.ImageType;
    +import org.apache.pdfbox.rendering.PDFRenderer;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.FlattenRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L34">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class FlattenController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L41">    public FlattenController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L42">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L43">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/flatten&quot;)
    +    @Operation(
    +            summary = &quot;Flatten PDF form fields or full page&quot;,
    +            description =
    +                    &quot;Flattening just PDF form fields or converting each page to images to make text&quot;
    +                            + &quot; unselectable. Input:PDF, Output:PDF. Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; flatten(@ModelAttribute FlattenRequest request) throws Exception {
    +<span class="nc" id="L52">        MultipartFile file = request.getFileInput();</span>
    +
    +<span class="nc" id="L54">        PDDocument document = pdfDocumentFactory.load(file);</span>
    +<span class="nc" id="L55">        Boolean flattenOnlyForms = request.getFlattenOnlyForms();</span>
    +
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">        if (Boolean.TRUE.equals(flattenOnlyForms)) {</span>
    +<span class="nc" id="L58">            PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();</span>
    +<span class="nc bnc" id="L59" title="All 2 branches missed.">            if (acroForm != null) {</span>
    +<span class="nc" id="L60">                acroForm.flatten();</span>
    +            }
    +<span class="nc" id="L62">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +<span class="nc" id="L63">                    document, Filenames.toSimpleFileName(file.getOriginalFilename()));</span>
    +        } else {
    +            // flatten whole page aka convert each page to image and readd it (making text
    +            // unselectable)
    +<span class="nc" id="L67">            PDFRenderer pdfRenderer = new PDFRenderer(document);</span>
    +<span class="nc" id="L68">            PDDocument newDocument =</span>
    +<span class="nc" id="L69">                    pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document);</span>
    +<span class="nc" id="L70">            int numPages = document.getNumberOfPages();</span>
    +<span class="nc bnc" id="L71" title="All 2 branches missed.">            for (int i = 0; i &lt; numPages; i++) {</span>
    +                try {
    +<span class="nc" id="L73">                    BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300, ImageType.RGB);</span>
    +<span class="nc" id="L74">                    PDPage page = new PDPage();</span>
    +<span class="nc" id="L75">                    page.setMediaBox(document.getPage(i).getMediaBox());</span>
    +<span class="nc" id="L76">                    newDocument.addPage(page);</span>
    +<span class="nc" id="L77">                    try (PDPageContentStream contentStream =</span>
    +                            new PDPageContentStream(newDocument, page)) {
    +<span class="nc" id="L79">                        PDImageXObject pdImage = JPEGFactory.createFromImage(newDocument, image);</span>
    +<span class="nc" id="L80">                        float pageWidth = page.getMediaBox().getWidth();</span>
    +<span class="nc" id="L81">                        float pageHeight = page.getMediaBox().getHeight();</span>
    +
    +<span class="nc" id="L83">                        contentStream.drawImage(pdImage, 0, 0, pageWidth, pageHeight);</span>
    +                    }
    +<span class="nc" id="L85">                } catch (IOException e) {</span>
    +<span class="nc" id="L86">                    log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L87">                }</span>
    +            }
    +<span class="nc" id="L89">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +<span class="nc" id="L90">                    newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/MetadataController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/MetadataController.html
    new file mode 100644
    index 000000000..7650d5d42
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/MetadataController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetadataController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">MetadataController</span></div><h1>MetadataController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">345 of 345</td><td class="ctr2">0%</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="ctr1">26</td><td class="ctr2">26</td><td class="ctr1">102</td><td class="ctr2">102</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a2"><a href="MetadataController.java.html#L69" class="el_method">metadata(MetadataRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="319" alt="319"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="40" alt="40"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">21</td><td class="ctr2" id="g0">21</td><td class="ctr1" id="h0">93</td><td class="ctr2" id="i0">93</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="MetadataController.java.html#L45" class="el_method">checkUndefined(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="MetadataController.java.html#L55" class="el_method">initBinder(WebDataBinder)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="8" alt="8"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="MetadataController.java.html#L39" class="el_method">MetadataController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="MetadataController.java.html#L32" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/MetadataController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/MetadataController.java.html
    new file mode 100644
    index 000000000..2938e0e91
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/MetadataController.java.html
    @@ -0,0 +1,190 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetadataController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">MetadataController.java</span></div><h1>MetadataController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.io.IOException;
    +import java.text.ParseException;
    +import java.text.SimpleDateFormat;
    +import java.util.Calendar;
    +import java.util.Map;
    +import java.util.Map.Entry;
    +
    +import org.apache.pdfbox.cos.COSName;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDDocumentInformation;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.WebDataBinder;
    +import org.springframework.web.bind.annotation.*;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.MetadataRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +import stirling.software.SPDF.utils.propertyeditor.StringToMapPropertyEditor;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L32">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class MetadataController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L39">    public MetadataController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L40">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    private String checkUndefined(String entry) {
    +        // Check if the string is &quot;undefined&quot;
    +<span class="nc bnc" id="L45" title="All 2 branches missed.">        if (&quot;undefined&quot;.equals(entry)) {</span>
    +            // Return null if it is
    +<span class="nc" id="L47">            return null;</span>
    +        }
    +        // Return the original string if it's not &quot;undefined&quot;
    +<span class="nc" id="L50">        return entry;</span>
    +    }
    +
    +    @InitBinder
    +    public void initBinder(WebDataBinder binder) {
    +<span class="nc" id="L55">        binder.registerCustomEditor(Map.class, &quot;allRequestParams&quot;, new StringToMapPropertyEditor());</span>
    +<span class="nc" id="L56">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/update-metadata&quot;)
    +    @Operation(
    +            summary = &quot;Update metadata of a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint allows you to update the metadata of a given PDF file. You can&quot;
    +                            + &quot; add, modify, or delete standard and custom metadata fields. Input:PDF&quot;
    +                            + &quot; Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; metadata(@ModelAttribute MetadataRequest request)
    +            throws IOException {
    +
    +        // Extract PDF file from the request object
    +<span class="nc" id="L69">        MultipartFile pdfFile = request.getFileInput();</span>
    +
    +        // Extract metadata information
    +<span class="nc" id="L72">        Boolean deleteAll = request.isDeleteAll();</span>
    +<span class="nc" id="L73">        String author = request.getAuthor();</span>
    +<span class="nc" id="L74">        String creationDate = request.getCreationDate();</span>
    +<span class="nc" id="L75">        String creator = request.getCreator();</span>
    +<span class="nc" id="L76">        String keywords = request.getKeywords();</span>
    +<span class="nc" id="L77">        String modificationDate = request.getModificationDate();</span>
    +<span class="nc" id="L78">        String producer = request.getProducer();</span>
    +<span class="nc" id="L79">        String subject = request.getSubject();</span>
    +<span class="nc" id="L80">        String title = request.getTitle();</span>
    +<span class="nc" id="L81">        String trapped = request.getTrapped();</span>
    +
    +        // Extract additional custom parameters
    +<span class="nc" id="L84">        Map&lt;String, String&gt; allRequestParams = request.getAllRequestParams();</span>
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">        if (allRequestParams == null) {</span>
    +<span class="nc" id="L86">            allRequestParams = new java.util.HashMap&lt;String, String&gt;();</span>
    +        }
    +        // Load the PDF file into a PDDocument
    +<span class="nc" id="L89">        PDDocument document = pdfDocumentFactory.load(pdfFile, true);</span>
    +
    +        // Get the document information from the PDF
    +<span class="nc" id="L92">        PDDocumentInformation info = document.getDocumentInformation();</span>
    +
    +        // Check if each metadata value is &quot;undefined&quot; and set it to null if it is
    +<span class="nc" id="L95">        author = checkUndefined(author);</span>
    +<span class="nc" id="L96">        creationDate = checkUndefined(creationDate);</span>
    +<span class="nc" id="L97">        creator = checkUndefined(creator);</span>
    +<span class="nc" id="L98">        keywords = checkUndefined(keywords);</span>
    +<span class="nc" id="L99">        modificationDate = checkUndefined(modificationDate);</span>
    +<span class="nc" id="L100">        producer = checkUndefined(producer);</span>
    +<span class="nc" id="L101">        subject = checkUndefined(subject);</span>
    +<span class="nc" id="L102">        title = checkUndefined(title);</span>
    +<span class="nc" id="L103">        trapped = checkUndefined(trapped);</span>
    +
    +        // If the &quot;deleteAll&quot; flag is set, remove all metadata from the document
    +        // information
    +<span class="nc bnc" id="L107" title="All 2 branches missed.">        if (deleteAll) {</span>
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">            for (String key : info.getMetadataKeys()) {</span>
    +<span class="nc" id="L109">                info.setCustomMetadataValue(key, null);</span>
    +<span class="nc" id="L110">            }</span>
    +            // Remove metadata from the PDF history
    +<span class="nc" id="L112">            document.getDocumentCatalog().getCOSObject().removeItem(COSName.getPDFName(&quot;Metadata&quot;));</span>
    +<span class="nc" id="L113">            document.getDocumentCatalog()</span>
    +<span class="nc" id="L114">                    .getCOSObject()</span>
    +<span class="nc" id="L115">                    .removeItem(COSName.getPDFName(&quot;PieceInfo&quot;));</span>
    +<span class="nc" id="L116">            author = null;</span>
    +<span class="nc" id="L117">            creationDate = null;</span>
    +<span class="nc" id="L118">            creator = null;</span>
    +<span class="nc" id="L119">            keywords = null;</span>
    +<span class="nc" id="L120">            modificationDate = null;</span>
    +<span class="nc" id="L121">            producer = null;</span>
    +<span class="nc" id="L122">            subject = null;</span>
    +<span class="nc" id="L123">            title = null;</span>
    +<span class="nc" id="L124">            trapped = null;</span>
    +        } else {
    +            // Iterate through the request parameters and set the metadata values
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">            for (Entry&lt;String, String&gt; entry : allRequestParams.entrySet()) {</span>
    +<span class="nc" id="L128">                String key = entry.getKey();</span>
    +                // Check if the key is a standard metadata key
    +<span class="nc bnc" id="L130" title="All 2 branches missed.">                if (!&quot;Author&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L131" title="All 2 branches missed.">                        &amp;&amp; !&quot;CreationDate&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L132" title="All 2 branches missed.">                        &amp;&amp; !&quot;Creator&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L133" title="All 2 branches missed.">                        &amp;&amp; !&quot;Keywords&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L134" title="All 2 branches missed.">                        &amp;&amp; !&quot;modificationDate&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L135" title="All 2 branches missed.">                        &amp;&amp; !&quot;Producer&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L136" title="All 2 branches missed.">                        &amp;&amp; !&quot;Subject&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L137" title="All 2 branches missed.">                        &amp;&amp; !&quot;Title&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L138" title="All 2 branches missed.">                        &amp;&amp; !&quot;Trapped&quot;.equalsIgnoreCase(key)</span>
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">                        &amp;&amp; !key.contains(&quot;customKey&quot;)</span>
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">                        &amp;&amp; !key.contains(&quot;customValue&quot;)) {</span>
    +<span class="nc" id="L141">                    info.setCustomMetadataValue(key, entry.getValue());</span>
    +<span class="nc bnc" id="L142" title="All 2 branches missed.">                } else if (key.contains(&quot;customKey&quot;)) {</span>
    +<span class="nc" id="L143">                    int number = Integer.parseInt(key.replaceAll(&quot;\\D&quot;, &quot;&quot;));</span>
    +<span class="nc" id="L144">                    String customKey = entry.getValue();</span>
    +<span class="nc" id="L145">                    String customValue = allRequestParams.get(&quot;customValue&quot; + number);</span>
    +<span class="nc" id="L146">                    info.setCustomMetadataValue(customKey, customValue);</span>
    +                }
    +<span class="nc" id="L148">            }</span>
    +        }
    +<span class="nc bnc" id="L150" title="All 4 branches missed.">        if (creationDate != null &amp;&amp; creationDate.length() &gt; 0) {</span>
    +<span class="nc" id="L151">            Calendar creationDateCal = Calendar.getInstance();</span>
    +            try {
    +<span class="nc" id="L153">                creationDateCal.setTime(</span>
    +<span class="nc" id="L154">                        new SimpleDateFormat(&quot;yyyy/MM/dd HH:mm:ss&quot;).parse(creationDate));</span>
    +<span class="nc" id="L155">            } catch (ParseException e) {</span>
    +<span class="nc" id="L156">                log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L157">            }</span>
    +<span class="nc" id="L158">            info.setCreationDate(creationDateCal);</span>
    +<span class="nc" id="L159">        } else {</span>
    +<span class="nc" id="L160">            info.setCreationDate(null);</span>
    +        }
    +<span class="nc bnc" id="L162" title="All 4 branches missed.">        if (modificationDate != null &amp;&amp; modificationDate.length() &gt; 0) {</span>
    +<span class="nc" id="L163">            Calendar modificationDateCal = Calendar.getInstance();</span>
    +            try {
    +<span class="nc" id="L165">                modificationDateCal.setTime(</span>
    +<span class="nc" id="L166">                        new SimpleDateFormat(&quot;yyyy/MM/dd HH:mm:ss&quot;).parse(modificationDate));</span>
    +<span class="nc" id="L167">            } catch (ParseException e) {</span>
    +<span class="nc" id="L168">                log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L169">            }</span>
    +<span class="nc" id="L170">            info.setModificationDate(modificationDateCal);</span>
    +<span class="nc" id="L171">        } else {</span>
    +<span class="nc" id="L172">            info.setModificationDate(null);</span>
    +        }
    +<span class="nc" id="L174">        info.setCreator(creator);</span>
    +<span class="nc" id="L175">        info.setKeywords(keywords);</span>
    +<span class="nc" id="L176">        info.setAuthor(author);</span>
    +<span class="nc" id="L177">        info.setProducer(producer);</span>
    +<span class="nc" id="L178">        info.setSubject(subject);</span>
    +<span class="nc" id="L179">        info.setTitle(title);</span>
    +<span class="nc" id="L180">        info.setTrapped(trapped);</span>
    +
    +<span class="nc" id="L182">        document.setDocumentInformation(info);</span>
    +<span class="nc" id="L183">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L185">                Filenames.toSimpleFileName(pdfFile.getOriginalFilename())</span>
    +<span class="nc" id="L186">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_metadata.pdf&quot;);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/OCRController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/OCRController.html
    new file mode 100644
    index 000000000..23edd5f78
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/OCRController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OCRController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">OCRController</span></div><h1>OCRController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">421 of 421</td><td class="ctr2">0%</td><td class="bar">25 of 25</td><td class="ctr2">0%</td><td class="ctr1">23</td><td class="ctr2">23</td><td class="ctr1">108</td><td class="ctr2">108</td><td class="ctr1">10</td><td class="ctr2">10</td></tr></tfoot><tbody><tr><td id="a8"><a href="OCRController.java.html#L78" class="el_method">processPdfWithOCR(ProcessPdfWithOcrRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="297" alt="297"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="17" alt="17"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">10</td><td class="ctr2" id="g0">10</td><td class="ctr1" id="h0">71</td><td class="ctr2" id="i0">71</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="OCRController.java.html#L185" class="el_method">addFileToZip(File, String, ZipOutputStream)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="39" alt="39"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">11</td><td class="ctr2" id="i1">11</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="OCRController.java.html#L56" class="el_method">getAvailableTesseractLanguages()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="25" alt="25"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">9</td><td class="ctr2" id="i2">9</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="OCRController.java.html#L203" class="el_method">deleteDirectory(Path)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="17" alt="17"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">7</td><td class="ctr2" id="i3">7</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="OCRController.java.html#L208" class="el_method">lambda$deleteDirectory$3(Path)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="11" alt="11"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h4">5</td><td class="ctr2" id="i4">5</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a7"><a href="OCRController.java.html#L49" class="el_method">OCRController(ApplicationProperties, CustomPDFDocumentFactory)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="9" alt="9"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h5">4</td><td class="ctr2" id="i5">4</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="OCRController.java.html#L64" class="el_method">lambda$getAvailableTesseractLanguages$2(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="8" alt="8"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a5"><a href="OCRController.java.html#L63" class="el_method">lambda$getAvailableTesseractLanguages$1(File)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a4"><a href="OCRController.java.html#L62" class="el_method">lambda$getAvailableTesseractLanguages$0(File)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="5" alt="5"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a9"><a href="OCRController.java.html#L40" class="el_method">static {...}</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/OCRController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/OCRController.java.html
    new file mode 100644
    index 000000000..473084eb0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/OCRController.java.html
    @@ -0,0 +1,218 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OCRController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">OCRController.java</span></div><h1>OCRController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.image.BufferedImage;
    +import java.io.*;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.*;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import javax.imageio.ImageIO;
    +
    +import org.apache.pdfbox.multipdf.PDFMergerUtility;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.rendering.PDFRenderer;
    +import org.apache.pdfbox.text.PDFTextStripper;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.BoundedLineReader;
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.api.misc.ProcessPdfWithOcrRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +<span class="nc" id="L40">@Slf4j</span>
    +public class OCRController {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    public OCRController(
    +            ApplicationProperties applicationProperties,
    +<span class="nc" id="L49">            CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L50">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L51">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L52">    }</span>
    +
    +    /** Gets the list of available Tesseract languages from the tessdata directory */
    +    public List&lt;String&gt; getAvailableTesseractLanguages() {
    +<span class="nc" id="L56">        String tessdataDir = applicationProperties.getSystem().getTessdataDir();</span>
    +<span class="nc" id="L57">        File[] files = new File(tessdataDir).listFiles();</span>
    +<span class="nc bnc" id="L58" title="All 2 branches missed.">        if (files == null) {</span>
    +<span class="nc" id="L59">            return Collections.emptyList();</span>
    +        }
    +<span class="nc" id="L61">        return Arrays.stream(files)</span>
    +<span class="nc" id="L62">                .filter(file -&gt; file.getName().endsWith(&quot;.traineddata&quot;))</span>
    +<span class="nc" id="L63">                .map(file -&gt; file.getName().replace(&quot;.traineddata&quot;, &quot;&quot;))</span>
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">                .filter(lang -&gt; !lang.equalsIgnoreCase(&quot;osd&quot;))</span>
    +<span class="nc" id="L65">                .toList();</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/ocr-pdf&quot;)
    +    @Operation(
    +            summary = &quot;Process PDF files with OCR using Tesseract&quot;,
    +            description =
    +                    &quot;Takes a PDF file as input, performs OCR using specified languages and OCR type&quot;
    +                            + &quot; (skip-text/force-ocr), and returns the processed PDF. Input:PDF&quot;
    +                            + &quot; Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; processPdfWithOCR(
    +            @ModelAttribute ProcessPdfWithOcrRequest request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L78">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L79">        List&lt;String&gt; languages = request.getLanguages();</span>
    +<span class="nc" id="L80">        String ocrType = request.getOcrType();</span>
    +<span class="nc" id="L81">        Path tempDir = Files.createTempDirectory(&quot;ocr_process&quot;);</span>
    +<span class="nc" id="L82">        Path tempInputFile = tempDir.resolve(&quot;input.pdf&quot;);</span>
    +<span class="nc" id="L83">        Path tempOutputDir = tempDir.resolve(&quot;output&quot;);</span>
    +<span class="nc" id="L84">        Path tempImagesDir = tempDir.resolve(&quot;images&quot;);</span>
    +<span class="nc" id="L85">        Path finalOutputFile = tempDir.resolve(&quot;final_output.pdf&quot;);</span>
    +<span class="nc" id="L86">        Files.createDirectories(tempOutputDir);</span>
    +<span class="nc" id="L87">        Files.createDirectories(tempImagesDir);</span>
    +<span class="nc" id="L88">        Process process = null;</span>
    +        try {
    +            // Save input file
    +<span class="nc" id="L91">            inputFile.transferTo(tempInputFile.toFile());</span>
    +<span class="nc" id="L92">            PDFMergerUtility merger = new PDFMergerUtility();</span>
    +<span class="nc" id="L93">            merger.setDestinationFileName(finalOutputFile.toString());</span>
    +<span class="nc" id="L94">            try (PDDocument document = pdfDocumentFactory.load(tempInputFile.toFile())) {</span>
    +<span class="nc" id="L95">                PDFRenderer pdfRenderer = new PDFRenderer(document);</span>
    +<span class="nc" id="L96">                int pageCount = document.getNumberOfPages();</span>
    +<span class="nc bnc" id="L97" title="All 2 branches missed.">                for (int pageNum = 0; pageNum &lt; pageCount; pageNum++) {</span>
    +<span class="nc" id="L98">                    PDPage page = document.getPage(pageNum);</span>
    +<span class="nc" id="L99">                    boolean hasText = false;</span>
    +                    // Check for existing text
    +<span class="nc" id="L101">                    try (PDDocument tempDoc = new PDDocument()) {</span>
    +<span class="nc" id="L102">                        tempDoc.addPage(page);</span>
    +<span class="nc" id="L103">                        PDFTextStripper stripper = new PDFTextStripper();</span>
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">                        hasText = !stripper.getText(tempDoc).trim().isEmpty();</span>
    +                    }
    +                    boolean shouldOcr =
    +<span class="nc bnc" id="L107" title="All 3 branches missed.">                            switch (ocrType) {</span>
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">                                case &quot;skip-text&quot; -&gt; !hasText;</span>
    +<span class="nc" id="L109">                                case &quot;force-ocr&quot; -&gt; true;</span>
    +<span class="nc" id="L110">                                default -&gt; true;</span>
    +                            };
    +<span class="nc" id="L112">                    Path pageOutputPath =</span>
    +<span class="nc" id="L113">                            tempOutputDir.resolve(String.format(&quot;page_%d.pdf&quot;, pageNum));</span>
    +<span class="nc bnc" id="L114" title="All 2 branches missed.">                    if (shouldOcr) {</span>
    +                        // Convert page to image
    +<span class="nc" id="L116">                        BufferedImage image = pdfRenderer.renderImageWithDPI(pageNum, 300);</span>
    +<span class="nc" id="L117">                        Path imagePath =</span>
    +<span class="nc" id="L118">                                tempImagesDir.resolve(String.format(&quot;page_%d.png&quot;, pageNum));</span>
    +<span class="nc" id="L119">                        ImageIO.write(image, &quot;png&quot;, imagePath.toFile());</span>
    +                        // Build OCR command
    +<span class="nc" id="L121">                        List&lt;String&gt; command = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L122">                        command.add(&quot;tesseract&quot;);</span>
    +<span class="nc" id="L123">                        command.add(imagePath.toString());</span>
    +<span class="nc" id="L124">                        command.add(</span>
    +                                tempOutputDir
    +<span class="nc" id="L126">                                        .resolve(String.format(&quot;page_%d&quot;, pageNum))</span>
    +<span class="nc" id="L127">                                        .toString());</span>
    +<span class="nc" id="L128">                        command.add(&quot;-l&quot;);</span>
    +<span class="nc" id="L129">                        command.add(String.join(&quot;+&quot;, languages));</span>
    +                        // Always output PDF
    +<span class="nc" id="L131">                        command.add(&quot;pdf&quot;);</span>
    +<span class="nc" id="L132">                        ProcessBuilder pb = new ProcessBuilder(command);</span>
    +<span class="nc" id="L133">                        process = pb.start();</span>
    +                        // Capture any error output
    +<span class="nc" id="L135">                        try (BufferedReader reader =</span>
    +                                new BufferedReader(
    +<span class="nc" id="L137">                                        new InputStreamReader(process.getErrorStream()))) {</span>
    +                            String line;
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">                            while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) {</span>
    +<span class="nc" id="L140">                                log.debug(&quot;Tesseract: {}&quot;, line);</span>
    +                            }
    +                        }
    +<span class="nc" id="L143">                        int exitCode = process.waitFor();</span>
    +<span class="nc bnc" id="L144" title="All 2 branches missed.">                        if (exitCode != 0) {</span>
    +<span class="nc" id="L145">                            throw new RuntimeException(</span>
    +                                    &quot;Tesseract failed with exit code: &quot; + exitCode);
    +                        }
    +                        // Add OCR'd PDF to merger
    +<span class="nc" id="L149">                        merger.addSource(pageOutputPath.toFile());</span>
    +<span class="nc" id="L150">                    } else {</span>
    +                        // Save original page without OCR
    +<span class="nc" id="L152">                        try (PDDocument pageDoc = new PDDocument()) {</span>
    +<span class="nc" id="L153">                            pageDoc.addPage(page);</span>
    +<span class="nc" id="L154">                            pageDoc.save(pageOutputPath.toFile());</span>
    +<span class="nc" id="L155">                            merger.addSource(pageOutputPath.toFile());</span>
    +                        }
    +                    }
    +                }
    +            }
    +            // Merge all pages into final PDF
    +<span class="nc" id="L161">            merger.mergeDocuments(null);</span>
    +            // Read the final PDF file
    +<span class="nc" id="L163">            byte[] pdfContent = Files.readAllBytes(finalOutputFile);</span>
    +<span class="nc" id="L164">            String outputFilename =</span>
    +<span class="nc" id="L165">                    Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +<span class="nc" id="L166">                                    .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                            + &quot;_OCR.pdf&quot;;
    +<span class="nc" id="L168">            return ResponseEntity.ok()</span>
    +<span class="nc" id="L169">                    .header(</span>
    +                            &quot;Content-Disposition&quot;,
    +                            &quot;attachment; filename=\&quot;&quot; + outputFilename + &quot;\&quot;&quot;)
    +<span class="nc" id="L172">                    .contentType(MediaType.APPLICATION_PDF)</span>
    +<span class="nc" id="L173">                    .body(pdfContent);</span>
    +        } finally {
    +<span class="nc bnc" id="L175" title="All 2 branches missed.">            if (process != null) {</span>
    +<span class="nc" id="L176">                process.destroy();</span>
    +            }
    +            // Clean up temporary files
    +<span class="nc" id="L179">            deleteDirectory(tempDir);</span>
    +        }
    +    }
    +
    +    private void addFileToZip(File file, String filename, ZipOutputStream zipOut)
    +            throws IOException {
    +<span class="nc bnc" id="L185" title="All 2 branches missed.">        if (!file.exists()) {</span>
    +<span class="nc" id="L186">            log.warn(&quot;File {} does not exist, skipping&quot;, file);</span>
    +<span class="nc" id="L187">            return;</span>
    +        }
    +<span class="nc" id="L189">        try (FileInputStream fis = new FileInputStream(file)) {</span>
    +<span class="nc" id="L190">            ZipEntry zipEntry = new ZipEntry(filename);</span>
    +<span class="nc" id="L191">            zipOut.putNextEntry(zipEntry);</span>
    +<span class="nc" id="L192">            byte[] buffer = new byte[1024];</span>
    +            int length;
    +<span class="nc bnc" id="L194" title="All 2 branches missed.">            while ((length = fis.read(buffer)) &gt;= 0) {</span>
    +<span class="nc" id="L195">                zipOut.write(buffer, 0, length);</span>
    +            }
    +<span class="nc" id="L197">            zipOut.closeEntry();</span>
    +        }
    +<span class="nc" id="L199">    }</span>
    +
    +    private void deleteDirectory(Path directory) {
    +        try {
    +<span class="nc" id="L203">            Files.walk(directory)</span>
    +<span class="nc" id="L204">                    .sorted(Comparator.reverseOrder())</span>
    +<span class="nc" id="L205">                    .forEach(</span>
    +                            path -&gt; {
    +                                try {
    +<span class="nc" id="L208">                                    Files.delete(path);</span>
    +<span class="nc" id="L209">                                } catch (IOException e) {</span>
    +<span class="nc" id="L210">                                    log.error(&quot;Error deleting {}: {}&quot;, path, e.getMessage());</span>
    +<span class="nc" id="L211">                                }</span>
    +<span class="nc" id="L212">                            });</span>
    +<span class="nc" id="L213">        } catch (IOException e) {</span>
    +<span class="nc" id="L214">            log.error(&quot;Error walking directory {}: {}&quot;, directory, e.getMessage());</span>
    +<span class="nc" id="L215">        }</span>
    +<span class="nc" id="L216">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/OverlayImageController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/OverlayImageController.html
    new file mode 100644
    index 000000000..9f426260e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/OverlayImageController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OverlayImageController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">OverlayImageController</span></div><h1>OverlayImageController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">60 of 60</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">19</td><td class="ctr2">19</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="OverlayImageController.java.html#L46" class="el_method">overlayImage(OverlayImageRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="50" alt="50"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">15</td><td class="ctr2" id="i0">15</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="OverlayImageController.java.html#L34" class="el_method">OverlayImageController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="OverlayImageController.java.html#L27" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/OverlayImageController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/OverlayImageController.java.html
    new file mode 100644
    index 000000000..88eee3788
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/OverlayImageController.java.html
    @@ -0,0 +1,69 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OverlayImageController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">OverlayImageController.java</span></div><h1>OverlayImageController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.io.IOException;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.OverlayImageRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.PdfUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +<span class="nc" id="L27">@Slf4j</span>
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class OverlayImageController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L34">    public OverlayImageController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L35">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L36">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/add-image&quot;)
    +    @Operation(
    +            summary = &quot;Overlay image onto a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint overlays an image onto a PDF file at the specified coordinates.&quot;
    +                            + &quot; The image can be overlaid on every page of the PDF if specified. &quot;
    +                            + &quot; Input:PDF/IMAGE Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; overlayImage(@ModelAttribute OverlayImageRequest request) {
    +<span class="nc" id="L46">        MultipartFile pdfFile = request.getFileInput();</span>
    +<span class="nc" id="L47">        MultipartFile imageFile = request.getImageFile();</span>
    +<span class="nc" id="L48">        float x = request.getX();</span>
    +<span class="nc" id="L49">        float y = request.getY();</span>
    +<span class="nc" id="L50">        boolean everyPage = request.isEveryPage();</span>
    +        try {
    +<span class="nc" id="L52">            byte[] pdfBytes = pdfFile.getBytes();</span>
    +<span class="nc" id="L53">            byte[] imageBytes = imageFile.getBytes();</span>
    +<span class="nc" id="L54">            byte[] result =</span>
    +<span class="nc" id="L55">                    PdfUtils.overlayImage(</span>
    +                            pdfDocumentFactory, pdfBytes, imageBytes, x, y, everyPage);
    +
    +<span class="nc" id="L58">            return WebResponseUtils.bytesToWebResponse(</span>
    +                    result,
    +<span class="nc" id="L60">                    Filenames.toSimpleFileName(pdfFile.getOriginalFilename())</span>
    +<span class="nc" id="L61">                                    .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                            + &quot;_overlayed.pdf&quot;);
    +<span class="nc" id="L63">        } catch (IOException e) {</span>
    +<span class="nc" id="L64">            log.error(&quot;Failed to add image to PDF&quot;, e);</span>
    +<span class="nc" id="L65">            return new ResponseEntity&lt;&gt;(HttpStatus.BAD_REQUEST);</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/PageNumbersController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/PageNumbersController.html
    new file mode 100644
    index 000000000..f200af4b9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/PageNumbersController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PageNumbersController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">PageNumbersController</span></div><h1>PageNumbersController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">317 of 317</td><td class="ctr2">0%</td><td class="bar">26 of 26</td><td class="ctr2">0%</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">83</td><td class="ctr2">83</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="PageNumbersController.java.html#L52" class="el_method">addPageNumbers(AddPageNumbersRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="311" alt="311"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="26" alt="26"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">17</td><td class="ctr2" id="g0">17</td><td class="ctr1" id="h0">80</td><td class="ctr2" id="i0">80</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="PageNumbersController.java.html#L39" class="el_method">PageNumbersController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/PageNumbersController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/PageNumbersController.java.html
    new file mode 100644
    index 000000000..19077994d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/PageNumbersController.java.html
    @@ -0,0 +1,178 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PageNumbersController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">PageNumbersController.java</span></div><h1>PageNumbersController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.util.List;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.font.PDType1Font;
    +import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class PageNumbersController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L39">    public PageNumbersController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L40">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    @PostMapping(value = &quot;/add-page-numbers&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Add page numbers to a PDF document&quot;,
    +            description =
    +                    &quot;This operation takes an input PDF file and adds page numbers to it. Input:PDF&quot;
    +                            + &quot; Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
    +            throws IOException {
    +
    +<span class="nc" id="L52">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L53">        String customMargin = request.getCustomMargin();</span>
    +<span class="nc" id="L54">        int position = request.getPosition();</span>
    +<span class="nc" id="L55">        int startingNumber = request.getStartingNumber();</span>
    +<span class="nc" id="L56">        String pagesToNumber = request.getPagesToNumber();</span>
    +<span class="nc" id="L57">        String customText = request.getCustomText();</span>
    +<span class="nc" id="L58">        int pageNumber = startingNumber;</span>
    +<span class="nc" id="L59">        PDDocument document = pdfDocumentFactory.load(file);</span>
    +<span class="nc" id="L60">        float font_size = request.getFontSize();</span>
    +<span class="nc" id="L61">        String font_type = request.getFontType();</span>
    +        float marginFactor;
    +<span class="nc bnc" id="L63" title="All 5 branches missed.">        switch (customMargin.toLowerCase()) {</span>
    +            case &quot;small&quot;:
    +<span class="nc" id="L65">                marginFactor = 0.02f;</span>
    +<span class="nc" id="L66">                break;</span>
    +            case &quot;medium&quot;:
    +<span class="nc" id="L68">                marginFactor = 0.035f;</span>
    +<span class="nc" id="L69">                break;</span>
    +            case &quot;large&quot;:
    +<span class="nc" id="L71">                marginFactor = 0.05f;</span>
    +<span class="nc" id="L72">                break;</span>
    +            case &quot;x-large&quot;:
    +<span class="nc" id="L74">                marginFactor = 0.075f;</span>
    +<span class="nc" id="L75">                break;</span>
    +            default:
    +<span class="nc" id="L77">                marginFactor = 0.035f;</span>
    +                break;
    +        }
    +
    +<span class="nc" id="L81">        float fontSize = font_size;</span>
    +<span class="nc bnc" id="L82" title="All 4 branches missed.">        if (pagesToNumber == null || pagesToNumber.isEmpty()) {</span>
    +<span class="nc" id="L83">            pagesToNumber = &quot;all&quot;;</span>
    +        }
    +<span class="nc bnc" id="L85" title="All 4 branches missed.">        if (customText == null || customText.isEmpty()) {</span>
    +<span class="nc" id="L86">            customText = &quot;{n}&quot;;</span>
    +        }
    +<span class="nc" id="L88">        List&lt;Integer&gt; pagesToNumberList =</span>
    +<span class="nc" id="L89">                GeneralUtils.parsePageList(pagesToNumber.split(&quot;,&quot;), document.getNumberOfPages());</span>
    +
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">        for (int i : pagesToNumberList) {</span>
    +<span class="nc" id="L92">            PDPage page = document.getPage(i);</span>
    +<span class="nc" id="L93">            PDRectangle pageSize = page.getMediaBox();</span>
    +
    +<span class="nc" id="L95">            String text =</span>
    +                    customText
    +<span class="nc" id="L97">                            .replace(&quot;{n}&quot;, String.valueOf(pageNumber))</span>
    +<span class="nc" id="L98">                            .replace(&quot;{total}&quot;, String.valueOf(document.getNumberOfPages()))</span>
    +<span class="nc" id="L99">                            .replace(</span>
    +                                    &quot;{filename}&quot;,
    +<span class="nc" id="L101">                                    Filenames.toSimpleFileName(file.getOriginalFilename())</span>
    +<span class="nc" id="L102">                                            .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;));</span>
    +
    +            PDType1Font currentFont =
    +<span class="nc bnc" id="L105" title="All 3 branches missed.">                    switch (font_type.toLowerCase()) {</span>
    +<span class="nc" id="L106">                        case &quot;courier&quot; -&gt; new PDType1Font(Standard14Fonts.FontName.COURIER);</span>
    +<span class="nc" id="L107">                        case &quot;times&quot; -&gt; new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN);</span>
    +<span class="nc" id="L108">                        default -&gt; new PDType1Font(Standard14Fonts.FontName.HELVETICA);</span>
    +                    };
    +
    +            float x, y;
    +
    +<span class="nc bnc" id="L113" title="All 2 branches missed.">            if (position == 5) {</span>
    +                // Calculate text width and font metrics
    +<span class="nc" id="L115">                float textWidth = currentFont.getStringWidth(text) / 1000 * fontSize;</span>
    +
    +<span class="nc" id="L117">                float ascent = currentFont.getFontDescriptor().getAscent() / 1000 * fontSize;</span>
    +<span class="nc" id="L118">                float descent = currentFont.getFontDescriptor().getDescent() / 1000 * fontSize;</span>
    +
    +<span class="nc" id="L120">                float centerX = pageSize.getLowerLeftX() + (pageSize.getWidth() / 2);</span>
    +<span class="nc" id="L121">                float centerY = pageSize.getLowerLeftY() + (pageSize.getHeight() / 2);</span>
    +
    +<span class="nc" id="L123">                x = centerX - (textWidth / 2);</span>
    +<span class="nc" id="L124">                y = centerY - (ascent + descent) / 2;</span>
    +<span class="nc" id="L125">            } else {</span>
    +<span class="nc" id="L126">                int xGroup = (position - 1) % 3;</span>
    +<span class="nc" id="L127">                int yGroup = 2 - (position - 1) / 3;</span>
    +
    +<span class="nc bnc" id="L129" title="All 3 branches missed.">                x =</span>
    +                        switch (xGroup) {
    +                            case 0 -&gt;
    +<span class="nc" id="L132">                                    pageSize.getLowerLeftX()</span>
    +<span class="nc" id="L133">                                            + marginFactor * pageSize.getWidth(); // left</span>
    +                            case 1 -&gt;
    +<span class="nc" id="L135">                                    pageSize.getLowerLeftX() + (pageSize.getWidth() / 2); // center</span>
    +                            default -&gt;
    +<span class="nc" id="L137">                                    pageSize.getUpperRightX()</span>
    +<span class="nc" id="L138">                                            - marginFactor * pageSize.getWidth(); // right</span>
    +<span class="nc" id="L139">                        };</span>
    +
    +<span class="nc bnc" id="L141" title="All 3 branches missed.">                y =</span>
    +                        switch (yGroup) {
    +                            case 0 -&gt;
    +<span class="nc" id="L144">                                    pageSize.getLowerLeftY()</span>
    +<span class="nc" id="L145">                                            + marginFactor * pageSize.getHeight(); // bottom</span>
    +                            case 1 -&gt;
    +<span class="nc" id="L147">                                    pageSize.getLowerLeftY() + (pageSize.getHeight() / 2); // middle</span>
    +                            default -&gt;
    +<span class="nc" id="L149">                                    pageSize.getUpperRightY()</span>
    +<span class="nc" id="L150">                                            - marginFactor * pageSize.getHeight(); // top</span>
    +<span class="nc" id="L151">                        };</span>
    +            }
    +
    +<span class="nc" id="L154">            PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(
    +                            document, page, PDPageContentStream.AppendMode.APPEND, true, true);
    +<span class="nc" id="L157">            contentStream.beginText();</span>
    +<span class="nc" id="L158">            contentStream.setFont(currentFont, fontSize);</span>
    +<span class="nc" id="L159">            contentStream.newLineAtOffset(x, y);</span>
    +<span class="nc" id="L160">            contentStream.showText(text);</span>
    +<span class="nc" id="L161">            contentStream.endText();</span>
    +<span class="nc" id="L162">            contentStream.close();</span>
    +
    +<span class="nc" id="L164">            pageNumber++;</span>
    +<span class="nc" id="L165">        }</span>
    +
    +<span class="nc" id="L167">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L168">        document.save(baos);</span>
    +<span class="nc" id="L169">        document.close();</span>
    +
    +<span class="nc" id="L171">        return WebResponseUtils.bytesToWebResponse(</span>
    +<span class="nc" id="L172">                baos.toByteArray(),</span>
    +<span class="nc" id="L173">                Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_numbersAdded.pdf&quot;,
    +                MediaType.APPLICATION_PDF);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController$1.html b/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController$1.html
    new file mode 100644
    index 000000000..1bed7b9d7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PrintFileController.new Printable() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">PrintFileController.new Printable() {...}</span></div><h1>PrintFileController.new Printable() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">38 of 38</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="PrintFileController.java.html#L82" class="el_method">print(Graphics, PageFormat, int)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="29" alt="29"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">9</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="PrintFileController.java.html#L78" class="el_method">{...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController.html
    new file mode 100644
    index 000000000..8c00bb2e8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PrintFileController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">PrintFileController</span></div><h1>PrintFileController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">113 of 113</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">32</td><td class="ctr2">32</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a2"><a href="PrintFileController.java.html#L47" class="el_method">printFile(PrintFileRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="95" alt="95"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">28</td><td class="ctr2" id="i0">28</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PrintFileController.java.html#L57" class="el_method">lambda$printFile$0(String, PrintService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="PrintFileController.java.html#L61" class="el_method">lambda$printFile$1()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="5" alt="5"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="PrintFileController.java.html#L35" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="PrintFileController.java.html#L36" class="el_method">PrintFileController()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController.java.html
    new file mode 100644
    index 000000000..19a13d830
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/PrintFileController.java.html
    @@ -0,0 +1,108 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PrintFileController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">PrintFileController.java</span></div><h1>PrintFileController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.*;
    +import java.awt.image.BufferedImage;
    +import java.awt.print.PageFormat;
    +import java.awt.print.Printable;
    +import java.awt.print.PrinterException;
    +import java.awt.print.PrinterJob;
    +import java.io.IOException;
    +import java.util.Arrays;
    +
    +import javax.imageio.ImageIO;
    +import javax.print.PrintService;
    +import javax.print.PrintServiceLookup;
    +
    +import org.apache.pdfbox.Loader;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.printing.PDFPageable;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.PrintFileRequest;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +<span class="nc" id="L35">@Slf4j</span>
    +<span class="nc" id="L36">public class PrintFileController {</span>
    +
    +    // TODO
    +    // @PostMapping(value = &quot;/print-file&quot;, consumes = &quot;multipart/form-data&quot;)
    +    // @Operation(
    +    //        summary = &quot;Prints PDF/Image file to a set printer&quot;,
    +    //        description =
    +    //                &quot;Input of PDF or Image along with a printer name/URL/IP to match against to
    +    // send it to (Fire and forget) Input:Any Output:N/A Type:SISO&quot;)
    +    public ResponseEntity&lt;String&gt; printFile(@ModelAttribute PrintFileRequest request)
    +            throws IOException {
    +<span class="nc" id="L47">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L48">        String printerName = request.getPrinterName();</span>
    +<span class="nc" id="L49">        String contentType = file.getContentType();</span>
    +        try {
    +            // Find matching printer
    +<span class="nc" id="L52">            PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);</span>
    +<span class="nc" id="L53">            PrintService selectedService =</span>
    +<span class="nc" id="L54">                    Arrays.stream(services)</span>
    +<span class="nc" id="L55">                            .filter(</span>
    +                                    service -&gt;
    +<span class="nc" id="L57">                                            service.getName().toLowerCase().contains(printerName))</span>
    +<span class="nc" id="L58">                            .findFirst()</span>
    +<span class="nc" id="L59">                            .orElseThrow(</span>
    +                                    () -&gt;
    +<span class="nc" id="L61">                                            new IllegalArgumentException(</span>
    +                                                    &quot;No matching printer found&quot;));
    +
    +<span class="nc" id="L64">            log.info(&quot;Selected Printer: &quot; + selectedService.getName());</span>
    +
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">            if (&quot;application/pdf&quot;.equals(contentType)) {</span>
    +<span class="nc" id="L67">                PDDocument document = Loader.loadPDF(file.getBytes());</span>
    +<span class="nc" id="L68">                PrinterJob job = PrinterJob.getPrinterJob();</span>
    +<span class="nc" id="L69">                job.setPrintService(selectedService);</span>
    +<span class="nc" id="L70">                job.setPageable(new PDFPageable(document));</span>
    +<span class="nc" id="L71">                job.print();</span>
    +<span class="nc" id="L72">                document.close();</span>
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">            } else if (contentType.startsWith(&quot;image/&quot;)) {</span>
    +<span class="nc" id="L74">                BufferedImage image = ImageIO.read(file.getInputStream());</span>
    +<span class="nc" id="L75">                PrinterJob job = PrinterJob.getPrinterJob();</span>
    +<span class="nc" id="L76">                job.setPrintService(selectedService);</span>
    +<span class="nc" id="L77">                job.setPrintable(</span>
    +<span class="nc" id="L78">                        new Printable() {</span>
    +                            public int print(
    +                                    Graphics graphics, PageFormat pageFormat, int pageIndex)
    +                                    throws PrinterException {
    +<span class="nc bnc" id="L82" title="All 2 branches missed.">                                if (pageIndex != 0) {</span>
    +<span class="nc" id="L83">                                    return NO_SUCH_PAGE;</span>
    +                                }
    +<span class="nc" id="L85">                                Graphics2D g2d = (Graphics2D) graphics;</span>
    +<span class="nc" id="L86">                                g2d.translate(</span>
    +<span class="nc" id="L87">                                        pageFormat.getImageableX(), pageFormat.getImageableY());</span>
    +<span class="nc" id="L88">                                g2d.drawImage(</span>
    +                                        image,
    +                                        0,
    +                                        0,
    +<span class="nc" id="L92">                                        (int) pageFormat.getImageableWidth(),</span>
    +<span class="nc" id="L93">                                        (int) pageFormat.getImageableHeight(),</span>
    +                                        null);
    +<span class="nc" id="L95">                                return PAGE_EXISTS;</span>
    +                            }
    +                        });
    +<span class="nc" id="L98">                job.print();</span>
    +            }
    +<span class="nc" id="L100">            return new ResponseEntity&lt;&gt;(</span>
    +<span class="nc" id="L101">                    &quot;File printed successfully to &quot; + selectedService.getName(), HttpStatus.OK);</span>
    +<span class="nc" id="L102">        } catch (Exception e) {</span>
    +<span class="nc" id="L103">            System.err.println(&quot;Failed to print: &quot; + e.getMessage());</span>
    +<span class="nc" id="L104">            return new ResponseEntity&lt;&gt;(e.getMessage(), HttpStatus.BAD_REQUEST);</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/RepairController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/RepairController.html
    new file mode 100644
    index 000000000..da465926a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/RepairController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RepairController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">RepairController</span></div><h1>RepairController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">74 of 74</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">22</td><td class="ctr2">22</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="RepairController.java.html#L48" class="el_method">repairPdf(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="68" alt="68"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">19</td><td class="ctr2" id="i0">19</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="RepairController.java.html#L35" class="el_method">RepairController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/RepairController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/RepairController.java.html
    new file mode 100644
    index 000000000..025600d9c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/RepairController.java.html
    @@ -0,0 +1,81 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RepairController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">RepairController.java</span></div><h1>RepairController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.ProcessExecutor;
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class RepairController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L35">    public RepairController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L36">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L37">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/repair&quot;)
    +    @Operation(
    +            summary = &quot;Repair a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint repairs a given PDF file by running qpdf command. The PDF is&quot;
    +                            + &quot; first saved to a temporary location, repaired, read back, and then&quot;
    +                            + &quot; returned as a response. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; repairPdf(@ModelAttribute PDFFile request)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L48">        MultipartFile inputFile = request.getFileInput();</span>
    +        // Save the uploaded file to a temporary location
    +<span class="nc" id="L50">        Path tempInputFile = Files.createTempFile(&quot;input_&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L51">        byte[] pdfBytes = null;</span>
    +<span class="nc" id="L52">        inputFile.transferTo(tempInputFile.toFile());</span>
    +        try {
    +
    +<span class="nc" id="L55">            List&lt;String&gt; command = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L56">            command.add(&quot;qpdf&quot;);</span>
    +<span class="nc" id="L57">            command.add(&quot;--replace-input&quot;); // Automatically fixes problems it can</span>
    +<span class="nc" id="L58">            command.add(&quot;--qdf&quot;); // Linearizes and normalizes PDF structure</span>
    +<span class="nc" id="L59">            command.add(&quot;--object-streams=disable&quot;); // Can help with some corruptions</span>
    +<span class="nc" id="L60">            command.add(tempInputFile.toString());</span>
    +
    +<span class="nc" id="L62">            ProcessExecutorResult returnCode =</span>
    +<span class="nc" id="L63">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.QPDF)</span>
    +<span class="nc" id="L64">                            .runCommandWithOutputHandling(command);</span>
    +
    +            // Read the optimized PDF file
    +<span class="nc" id="L67">            pdfBytes = pdfDocumentFactory.loadToBytes(tempInputFile.toFile());</span>
    +
    +            // Return the optimized PDF as a response
    +<span class="nc" id="L70">            String outputFilename =</span>
    +<span class="nc" id="L71">                    Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +<span class="nc" id="L72">                                    .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                            + &quot;_repaired.pdf&quot;;
    +<span class="nc" id="L74">            return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename);</span>
    +        } finally {
    +            // Clean up the temporary files
    +<span class="nc" id="L77">            Files.deleteIfExists(tempInputFile);</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/ReplaceAndInvertColorController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/ReplaceAndInvertColorController.html
    new file mode 100644
    index 000000000..59e14150b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/ReplaceAndInvertColorController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvertColorController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">ReplaceAndInvertColorController</span></div><h1>ReplaceAndInvertColorController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">35 of 35</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvertColorController.java.html#L43" class="el_method">replaceAndInvertColor(ReplaceAndInvertColorRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="29" alt="29"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">11</td><td class="ctr2" id="i0">11</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ReplaceAndInvertColorController.java.html#L30" class="el_method">ReplaceAndInvertColorController(ReplaceAndInvertColorService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/ReplaceAndInvertColorController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/ReplaceAndInvertColorController.java.html
    new file mode 100644
    index 000000000..fa2cfbe80
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/ReplaceAndInvertColorController.java.html
    @@ -0,0 +1,58 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvertColorController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">ReplaceAndInvertColorController.java</span></div><h1>ReplaceAndInvertColorController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.io.IOException;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.core.io.InputStreamResource;
    +import org.springframework.http.HttpHeaders;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.misc.ReplaceAndInvertColorRequest;
    +import stirling.software.SPDF.service.misc.ReplaceAndInvertColorService;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class ReplaceAndInvertColorController {
    +
    +    private ReplaceAndInvertColorService replaceAndInvertColorService;
    +
    +    @Autowired
    +    public ReplaceAndInvertColorController(
    +<span class="nc" id="L30">            ReplaceAndInvertColorService replaceAndInvertColorService) {</span>
    +<span class="nc" id="L31">        this.replaceAndInvertColorService = replaceAndInvertColorService;</span>
    +<span class="nc" id="L32">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/replace-invert-pdf&quot;)
    +    @Operation(
    +            summary = &quot;Replace-Invert Color PDF&quot;,
    +            description =
    +                    &quot;This endpoint accepts a PDF file and option of invert all colors or replace text and background colors. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;InputStreamResource&gt; replaceAndInvertColor(
    +            @ModelAttribute ReplaceAndInvertColorRequest replaceAndInvertColorRequest)
    +            throws IOException {
    +
    +<span class="nc" id="L43">        InputStreamResource resource =</span>
    +<span class="nc" id="L44">                replaceAndInvertColorService.replaceAndInvertColor(</span>
    +<span class="nc" id="L45">                        replaceAndInvertColorRequest.getFileInput(),</span>
    +<span class="nc" id="L46">                        replaceAndInvertColorRequest.getReplaceAndInvertOption(),</span>
    +<span class="nc" id="L47">                        replaceAndInvertColorRequest.getHighContrastColorCombination(),</span>
    +<span class="nc" id="L48">                        replaceAndInvertColorRequest.getBackGroundColor(),</span>
    +<span class="nc" id="L49">                        replaceAndInvertColorRequest.getTextColor());</span>
    +
    +        // Return the modified PDF as a downloadable file
    +<span class="nc" id="L52">        return ResponseEntity.ok()</span>
    +<span class="nc" id="L53">                .header(HttpHeaders.CONTENT_DISPOSITION, &quot;attachment; filename=inverted.pdf&quot;)</span>
    +<span class="nc" id="L54">                .contentType(MediaType.APPLICATION_PDF)</span>
    +<span class="nc" id="L55">                .body(resource);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/ShowJavascript.html b/testResult/html/stirling.software.SPDF.controller.api.misc/ShowJavascript.html
    new file mode 100644
    index 000000000..4c2f44b0c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/ShowJavascript.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ShowJavascript</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">ShowJavascript</span></div><h1>ShowJavascript</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">84 of 84</td><td class="ctr2">0%</td><td class="bar">10 of 10</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">26</td><td class="ctr2">26</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ShowJavascript.java.html#L43" class="el_method">extractHeader(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="78" alt="78"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">23</td><td class="ctr2" id="i0">23</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ShowJavascript.java.html#L34" class="el_method">ShowJavascript(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/ShowJavascript.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/ShowJavascript.java.html
    new file mode 100644
    index 000000000..28f95c236
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/ShowJavascript.java.html
    @@ -0,0 +1,88 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ShowJavascript.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">ShowJavascript.java</span></div><h1>ShowJavascript.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.nio.charset.StandardCharsets;
    +import java.util.Map;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.common.PDNameTreeNode;
    +import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class ShowJavascript {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L34">    public ShowJavascript(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L35">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L36">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/show-javascript&quot;)
    +    @Operation(
    +            summary = &quot;Grabs all JS from a PDF and returns a single JS file with all code&quot;,
    +            description = &quot;desc. Input:PDF Output:JS Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; extractHeader(@ModelAttribute PDFFile request) throws Exception {
    +<span class="nc" id="L43">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L44">        String script = &quot;&quot;;</span>
    +
    +<span class="nc" id="L46">        try (PDDocument document = pdfDocumentFactory.load(inputFile)) {</span>
    +
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">            if (document.getDocumentCatalog() != null</span>
    +<span class="nc bnc" id="L49" title="All 2 branches missed.">                    &amp;&amp; document.getDocumentCatalog().getNames() != null) {</span>
    +<span class="nc" id="L50">                PDNameTreeNode&lt;PDActionJavaScript&gt; jsTree =</span>
    +<span class="nc" id="L51">                        document.getDocumentCatalog().getNames().getJavaScript();</span>
    +
    +<span class="nc bnc" id="L53" title="All 2 branches missed.">                if (jsTree != null) {</span>
    +<span class="nc" id="L54">                    Map&lt;String, PDActionJavaScript&gt; jsEntries = jsTree.getNames();</span>
    +
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">                    for (Map.Entry&lt;String, PDActionJavaScript&gt; entry : jsEntries.entrySet()) {</span>
    +<span class="nc" id="L57">                        String name = entry.getKey();</span>
    +<span class="nc" id="L58">                        PDActionJavaScript jsAction = entry.getValue();</span>
    +<span class="nc" id="L59">                        String jsCodeStr = jsAction.getAction();</span>
    +
    +<span class="nc" id="L61">                        script +=</span>
    +                                &quot;// File: &quot;
    +<span class="nc" id="L63">                                        + Filenames.toSimpleFileName(</span>
    +<span class="nc" id="L64">                                                inputFile.getOriginalFilename())</span>
    +                                        + &quot;, Script: &quot;
    +                                        + name
    +                                        + &quot;\n&quot;
    +                                        + jsCodeStr
    +                                        + &quot;\n&quot;;
    +<span class="nc" id="L70">                    }</span>
    +                }
    +            }
    +
    +<span class="nc bnc" id="L74" title="All 2 branches missed.">            if (script.isEmpty()) {</span>
    +<span class="nc" id="L75">                script =</span>
    +                        &quot;PDF '&quot;
    +<span class="nc" id="L77">                                + Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +                                + &quot;' does not contain Javascript&quot;;
    +            }
    +
    +<span class="nc" id="L81">            return WebResponseUtils.bytesToWebResponse(</span>
    +<span class="nc" id="L82">                    script.getBytes(StandardCharsets.UTF_8),</span>
    +<span class="nc" id="L83">                    Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + &quot;.js&quot;,</span>
    +                    MediaType.TEXT_PLAIN);
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/StampController.html b/testResult/html/stirling.software.SPDF.controller.api.misc/StampController.html
    new file mode 100644
    index 000000000..9529ad968
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/StampController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StampController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_class">StampController</span></div><h1>StampController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">523 of 523</td><td class="ctr2">0%</td><td class="bar">46 of 46</td><td class="ctr2">0%</td><td class="ctr1">36</td><td class="ctr2">36</td><td class="ctr1">126</td><td class="ctr2">126</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a2"><a href="StampController.java.html#L170" class="el_method">addTextStamp(PDPageContentStream, String, PDDocument, PDPage, float, int, float, String, float, float, float, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="183" alt="183"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="17" alt="17"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h0">50</td><td class="ctr2" id="i0">50</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="StampController.java.html#L65" class="el_method">addStamp(AddStampRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="115" height="10" title="176" alt="176"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="105" height="10" title="15" alt="15"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">10</td><td class="ctr2" id="g1">10</td><td class="ctr1" id="h1">43</td><td class="ctr2" id="i1">43</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="StampController.java.html#L267" class="el_method">addImageStamp(PDPageContentStream, MultipartFile, PDDocument, PDPage, float, int, float, float, float, float)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="52" height="10" title="80" alt="80"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h2">17</td><td class="ctr2" id="i2">17</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="StampController.java.html#L310" class="el_method">calculatePositionX(PDRectangle, int, float, PDFont, float, String, float)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="35" alt="35"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h3">6</td><td class="ctr2" id="i3">6</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="StampController.java.html#L325" class="el_method">calculatePositionY(PDRectangle, int, float, float)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="27" alt="27"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">4</td><td class="ctr2" id="g3">4</td><td class="ctr1" id="h4">5</td><td class="ctr2" id="i4">5</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="StampController.java.html#L338" class="el_method">calculateTextWidth(String, PDFont, float)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="8" alt="8"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a5"><a href="StampController.java.html#L342" class="el_method">calculateTextCapHeight(PDFont, float)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="8" alt="8"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a7"><a href="StampController.java.html#L52" class="el_method">StampController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="6" alt="6"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/StampController.java.html b/testResult/html/stirling.software.SPDF.controller.api.misc/StampController.java.html
    new file mode 100644
    index 000000000..67bad304c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/StampController.java.html
    @@ -0,0 +1,345 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StampController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.misc</a> &gt; <span class="el_source">StampController.java</span></div><h1>StampController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.misc;
    +
    +import java.awt.*;
    +import java.awt.image.BufferedImage;
    +import java.io.File;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.nio.file.Files;
    +import java.util.List;
    +
    +import javax.imageio.ImageIO;
    +
    +import org.apache.commons.io.IOUtils;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.font.PDFont;
    +import org.apache.pdfbox.pdmodel.font.PDType0Font;
    +import org.apache.pdfbox.pdmodel.font.PDType1Font;
    +import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
    +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
    +import org.apache.pdfbox.util.Matrix;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.misc.AddStampRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/misc&quot;)
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class StampController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L52">    public StampController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L53">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L54">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/add-stamp&quot;)
    +    @Operation(
    +            summary = &quot;Add stamp to a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint adds a stamp to a given PDF file. Users can specify the stamp&quot;
    +                            + &quot; type (text or image), rotation, opacity, width spacer, and height&quot;
    +                            + &quot; spacer. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; addStamp(@ModelAttribute AddStampRequest request)
    +            throws IOException, Exception {
    +<span class="nc" id="L65">        MultipartFile pdfFile = request.getFileInput();</span>
    +<span class="nc" id="L66">        String stampType = request.getStampType();</span>
    +<span class="nc" id="L67">        String stampText = request.getStampText();</span>
    +<span class="nc" id="L68">        MultipartFile stampImage = request.getStampImage();</span>
    +<span class="nc" id="L69">        String alphabet = request.getAlphabet();</span>
    +<span class="nc" id="L70">        float fontSize = request.getFontSize();</span>
    +<span class="nc" id="L71">        float rotation = request.getRotation();</span>
    +<span class="nc" id="L72">        float opacity = request.getOpacity();</span>
    +<span class="nc" id="L73">        int position = request.getPosition(); // Updated to use 1-9 positioning logic</span>
    +<span class="nc" id="L74">        float overrideX = request.getOverrideX(); // New field for X override</span>
    +<span class="nc" id="L75">        float overrideY = request.getOverrideY(); // New field for Y override</span>
    +
    +<span class="nc" id="L77">        String customColor = request.getCustomColor();</span>
    +        float marginFactor;
    +
    +<span class="nc bnc" id="L80" title="All 5 branches missed.">        switch (request.getCustomMargin().toLowerCase()) {</span>
    +            case &quot;small&quot;:
    +<span class="nc" id="L82">                marginFactor = 0.02f;</span>
    +<span class="nc" id="L83">                break;</span>
    +            case &quot;medium&quot;:
    +<span class="nc" id="L85">                marginFactor = 0.035f;</span>
    +<span class="nc" id="L86">                break;</span>
    +            case &quot;large&quot;:
    +<span class="nc" id="L88">                marginFactor = 0.05f;</span>
    +<span class="nc" id="L89">                break;</span>
    +            case &quot;x-large&quot;:
    +<span class="nc" id="L91">                marginFactor = 0.075f;</span>
    +<span class="nc" id="L92">                break;</span>
    +            default:
    +<span class="nc" id="L94">                marginFactor = 0.035f;</span>
    +                break;
    +        }
    +
    +        // Load the input PDF
    +<span class="nc" id="L99">        PDDocument document = pdfDocumentFactory.load(pdfFile);</span>
    +
    +<span class="nc" id="L101">        List&lt;Integer&gt; pageNumbers = request.getPageNumbersList(document, true);</span>
    +
    +<span class="nc bnc" id="L103" title="All 2 branches missed.">        for (int pageIndex : pageNumbers) {</span>
    +<span class="nc" id="L104">            int zeroBasedIndex = pageIndex - 1;</span>
    +<span class="nc bnc" id="L105" title="All 4 branches missed.">            if (zeroBasedIndex &gt;= 0 &amp;&amp; zeroBasedIndex &lt; document.getNumberOfPages()) {</span>
    +<span class="nc" id="L106">                PDPage page = document.getPage(zeroBasedIndex);</span>
    +<span class="nc" id="L107">                PDRectangle pageSize = page.getMediaBox();</span>
    +<span class="nc" id="L108">                float margin = marginFactor * (pageSize.getWidth() + pageSize.getHeight()) / 2;</span>
    +
    +<span class="nc" id="L110">                PDPageContentStream contentStream =</span>
    +                        new PDPageContentStream(
    +                                document, page, PDPageContentStream.AppendMode.APPEND, true, true);
    +
    +<span class="nc" id="L114">                PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();</span>
    +<span class="nc" id="L115">                graphicsState.setNonStrokingAlphaConstant(opacity);</span>
    +<span class="nc" id="L116">                contentStream.setGraphicsStateParameters(graphicsState);</span>
    +
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">                if (&quot;text&quot;.equalsIgnoreCase(stampType)) {</span>
    +<span class="nc" id="L119">                    addTextStamp(</span>
    +                            contentStream,
    +                            stampText,
    +                            document,
    +                            page,
    +                            rotation,
    +                            position,
    +                            fontSize,
    +                            alphabet,
    +                            overrideX,
    +                            overrideY,
    +                            margin,
    +                            customColor);
    +<span class="nc bnc" id="L132" title="All 2 branches missed.">                } else if (&quot;image&quot;.equalsIgnoreCase(stampType)) {</span>
    +<span class="nc" id="L133">                    addImageStamp(</span>
    +                            contentStream,
    +                            stampImage,
    +                            document,
    +                            page,
    +                            rotation,
    +                            position,
    +                            fontSize,
    +                            overrideX,
    +                            overrideY,
    +                            margin);
    +                }
    +
    +<span class="nc" id="L146">                contentStream.close();</span>
    +            }
    +<span class="nc" id="L148">        }</span>
    +<span class="nc" id="L149">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L151">                Filenames.toSimpleFileName(pdfFile.getOriginalFilename())</span>
    +<span class="nc" id="L152">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_stamped.pdf&quot;);
    +    }
    +
    +    private void addTextStamp(
    +            PDPageContentStream contentStream,
    +            String stampText,
    +            PDDocument document,
    +            PDPage page,
    +            float rotation,
    +            int position, // 1-9 positioning logic
    +            float fontSize,
    +            String alphabet,
    +            float overrideX, // X override
    +            float overrideY,
    +            float margin,
    +            String colorString) // Y override
    +            throws IOException {
    +<span class="nc" id="L170">        String resourceDir = &quot;&quot;;</span>
    +<span class="nc" id="L171">        PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);</span>
    +<span class="nc bnc" id="L172" title="All 5 branches missed.">        switch (alphabet) {</span>
    +            case &quot;arabic&quot;:
    +<span class="nc" id="L174">                resourceDir = &quot;static/fonts/NotoSansArabic-Regular.ttf&quot;;</span>
    +<span class="nc" id="L175">                break;</span>
    +            case &quot;japanese&quot;:
    +<span class="nc" id="L177">                resourceDir = &quot;static/fonts/Meiryo.ttf&quot;;</span>
    +<span class="nc" id="L178">                break;</span>
    +            case &quot;korean&quot;:
    +<span class="nc" id="L180">                resourceDir = &quot;static/fonts/malgun.ttf&quot;;</span>
    +<span class="nc" id="L181">                break;</span>
    +            case &quot;chinese&quot;:
    +<span class="nc" id="L183">                resourceDir = &quot;static/fonts/SimSun.ttf&quot;;</span>
    +<span class="nc" id="L184">                break;</span>
    +            case &quot;roman&quot;:
    +            default:
    +<span class="nc" id="L187">                resourceDir = &quot;static/fonts/NotoSans-Regular.ttf&quot;;</span>
    +                break;
    +        }
    +
    +<span class="nc bnc" id="L191" title="All 2 branches missed.">        if (!&quot;&quot;.equals(resourceDir)) {</span>
    +<span class="nc" id="L192">            ClassPathResource classPathResource = new ClassPathResource(resourceDir);</span>
    +<span class="nc" id="L193">            String fileExtension = resourceDir.substring(resourceDir.lastIndexOf(&quot;.&quot;));</span>
    +<span class="nc" id="L194">            File tempFile = Files.createTempFile(&quot;NotoSansFont&quot;, fileExtension).toFile();</span>
    +<span class="nc" id="L195">            try (InputStream is = classPathResource.getInputStream();</span>
    +<span class="nc" id="L196">                    FileOutputStream os = new FileOutputStream(tempFile)) {</span>
    +<span class="nc" id="L197">                IOUtils.copy(is, os);</span>
    +<span class="nc" id="L198">                font = PDType0Font.load(document, tempFile);</span>
    +            } finally {
    +<span class="nc bnc" id="L200" title="All 2 branches missed.">                if (tempFile != null) {</span>
    +<span class="nc" id="L201">                    Files.deleteIfExists(tempFile.toPath());</span>
    +                }
    +            }
    +        }
    +
    +<span class="nc" id="L206">        contentStream.setFont(font, fontSize);</span>
    +
    +        Color redactColor;
    +        try {
    +<span class="nc bnc" id="L210" title="All 2 branches missed.">            if (!colorString.startsWith(&quot;#&quot;)) {</span>
    +<span class="nc" id="L211">                colorString = &quot;#&quot; + colorString;</span>
    +            }
    +<span class="nc" id="L213">            redactColor = Color.decode(colorString);</span>
    +<span class="nc" id="L214">        } catch (NumberFormatException e) {</span>
    +
    +<span class="nc" id="L216">            redactColor = Color.LIGHT_GRAY;</span>
    +<span class="nc" id="L217">        }</span>
    +
    +<span class="nc" id="L219">        contentStream.setNonStrokingColor(redactColor);</span>
    +
    +<span class="nc" id="L221">        PDRectangle pageSize = page.getMediaBox();</span>
    +        float x, y;
    +
    +<span class="nc bnc" id="L224" title="All 4 branches missed.">        if (overrideX &gt;= 0 &amp;&amp; overrideY &gt;= 0) {</span>
    +            // Use override values if provided
    +<span class="nc" id="L226">            x = overrideX;</span>
    +<span class="nc" id="L227">            y = overrideY;</span>
    +        } else {
    +<span class="nc" id="L229">            x = calculatePositionX(pageSize, position, fontSize, font, fontSize, stampText, margin);</span>
    +<span class="nc" id="L230">            y =</span>
    +<span class="nc" id="L231">                    calculatePositionY(</span>
    +<span class="nc" id="L232">                            pageSize, position, calculateTextCapHeight(font, fontSize), margin);</span>
    +        }
    +        // Split the stampText into multiple lines
    +<span class="nc" id="L235">        String[] lines = stampText.split(&quot;\\\\n&quot;);</span>
    +
    +        // Calculate dynamic line height based on font ascent and descent
    +<span class="nc" id="L238">        float ascent = font.getFontDescriptor().getAscent();</span>
    +<span class="nc" id="L239">        float descent = font.getFontDescriptor().getDescent();</span>
    +<span class="nc" id="L240">        float lineHeight = ((ascent - descent) / 1000) * fontSize;</span>
    +
    +<span class="nc" id="L242">        contentStream.beginText();</span>
    +<span class="nc bnc" id="L243" title="All 2 branches missed.">        for (int i = 0; i &lt; lines.length; i++) {</span>
    +<span class="nc" id="L244">            String line = lines[i];</span>
    +            // Set the text matrix for each line with rotation
    +<span class="nc" id="L246">            contentStream.setTextMatrix(</span>
    +<span class="nc" id="L247">                    Matrix.getRotateInstance(Math.toRadians(rotation), x, y - (i * lineHeight)));</span>
    +<span class="nc" id="L248">            contentStream.showText(line);</span>
    +        }
    +<span class="nc" id="L250">        contentStream.endText();</span>
    +<span class="nc" id="L251">    }</span>
    +
    +    private void addImageStamp(
    +            PDPageContentStream contentStream,
    +            MultipartFile stampImage,
    +            PDDocument document,
    +            PDPage page,
    +            float rotation,
    +            int position, // 1-9 positioning logic
    +            float fontSize,
    +            float overrideX,
    +            float overrideY,
    +            float margin)
    +            throws IOException {
    +
    +        // Load the stamp image
    +<span class="nc" id="L267">        BufferedImage image = ImageIO.read(stampImage.getInputStream());</span>
    +
    +        // Compute width based on original aspect ratio
    +<span class="nc" id="L270">        float aspectRatio = (float) image.getWidth() / (float) image.getHeight();</span>
    +
    +        // Desired physical height (in PDF points)
    +<span class="nc" id="L273">        float desiredPhysicalHeight = fontSize;</span>
    +
    +        // Desired physical width based on the aspect ratio
    +<span class="nc" id="L276">        float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio;</span>
    +
    +        // Convert the BufferedImage to PDImageXObject
    +<span class="nc" id="L279">        PDImageXObject xobject = LosslessFactory.createFromImage(document, image);</span>
    +
    +<span class="nc" id="L281">        PDRectangle pageSize = page.getMediaBox();</span>
    +        float x, y;
    +
    +<span class="nc bnc" id="L284" title="All 4 branches missed.">        if (overrideX &gt;= 0 &amp;&amp; overrideY &gt;= 0) {</span>
    +            // Use override values if provided
    +<span class="nc" id="L286">            x = overrideX;</span>
    +<span class="nc" id="L287">            y = overrideY;</span>
    +        } else {
    +<span class="nc" id="L289">            x = calculatePositionX(pageSize, position, desiredPhysicalWidth, null, 0, null, margin);</span>
    +<span class="nc" id="L290">            y = calculatePositionY(pageSize, position, fontSize, margin);</span>
    +        }
    +
    +<span class="nc" id="L293">        contentStream.saveGraphicsState();</span>
    +<span class="nc" id="L294">        contentStream.transform(Matrix.getTranslateInstance(x, y));</span>
    +<span class="nc" id="L295">        contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));</span>
    +<span class="nc" id="L296">        contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);</span>
    +<span class="nc" id="L297">        contentStream.restoreGraphicsState();</span>
    +<span class="nc" id="L298">    }</span>
    +
    +    private float calculatePositionX(
    +            PDRectangle pageSize,
    +            int position,
    +            float contentWidth,
    +            PDFont font,
    +            float fontSize,
    +            String text,
    +            float margin)
    +            throws IOException {
    +        float actualWidth =
    +<span class="nc bnc" id="L310" title="All 2 branches missed.">                (text != null) ? calculateTextWidth(text, font, fontSize) : contentWidth;</span>
    +<span class="nc bnc" id="L311" title="All 4 branches missed.">        switch (position % 3) {</span>
    +            case 1: // Left
    +<span class="nc" id="L313">                return pageSize.getLowerLeftX() + margin;</span>
    +            case 2: // Center
    +<span class="nc" id="L315">                return (pageSize.getWidth() - actualWidth) / 2;</span>
    +            case 0: // Right
    +<span class="nc" id="L317">                return pageSize.getUpperRightX() - actualWidth - margin;</span>
    +            default:
    +<span class="nc" id="L319">                return 0;</span>
    +        }
    +    }
    +
    +    private float calculatePositionY(
    +            PDRectangle pageSize, int position, float height, float margin) {
    +<span class="nc bnc" id="L325" title="All 4 branches missed.">        switch ((position - 1) / 3) {</span>
    +            case 0: // Top
    +<span class="nc" id="L327">                return pageSize.getUpperRightY() - height - margin;</span>
    +            case 1: // Middle
    +<span class="nc" id="L329">                return (pageSize.getHeight() - height) / 2;</span>
    +            case 2: // Bottom
    +<span class="nc" id="L331">                return pageSize.getLowerLeftY() + margin;</span>
    +            default:
    +<span class="nc" id="L333">                return 0;</span>
    +        }
    +    }
    +
    +    private float calculateTextWidth(String text, PDFont font, float fontSize) throws IOException {
    +<span class="nc" id="L338">        return font.getStringWidth(text) / 1000 * fontSize;</span>
    +    }
    +
    +    private float calculateTextCapHeight(PDFont font, float fontSize) {
    +<span class="nc" id="L342">        return font.getFontDescriptor().getCapHeight() / 1000 * fontSize;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/index.html b/testResult/html/stirling.software.SPDF.controller.api.misc/index.html
    new file mode 100644
    index 000000000..49a408553
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.misc</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.misc</span></div><h1>stirling.software.SPDF.controller.api.misc</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">6,771 of 6,771</td><td class="ctr2">0%</td><td class="bar">534 of 534</td><td class="ctr2">0%</td><td class="ctr1">402</td><td class="ctr2">402</td><td class="ctr1">1,584</td><td class="ctr2">1,584</td><td class="ctr1">123</td><td class="ctr2">123</td><td class="ctr1">22</td><td class="ctr2">22</td></tr></tfoot><tbody><tr><td id="a5"><a href="CompressController.html" class="el_class">CompressController</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,769" alt="1,769"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="173" alt="173"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">119</td><td class="ctr2" id="g0">119</td><td class="ctr1" id="h0">396</td><td class="ctr2" id="i0">396</td><td class="ctr1" id="j0">29</td><td class="ctr2" id="k0">29</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a10"><a href="FakeScanControllerWIP.html" class="el_class">FakeScanControllerWIP</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="886" alt="886"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="34" alt="34"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">26</td><td class="ctr2" id="g2">26</td><td class="ctr1" id="h1">139</td><td class="ctr2" id="i1">139</td><td class="ctr1" id="j2">9</td><td class="ctr2" id="k2">9</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a21"><a href="StampController.html" class="el_class">StampController</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="523" alt="523"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="46" alt="46"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">36</td><td class="ctr2" id="g1">36</td><td class="ctr1" id="h2">126</td><td class="ctr2" id="i2">126</td><td class="ctr1" id="j3">8</td><td class="ctr2" id="k3">8</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a13"><a href="OCRController.html" class="el_class">OCRController</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="421" alt="421"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="25" alt="25"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f5">23</td><td class="ctr2" id="g5">23</td><td class="ctr1" id="h3">108</td><td class="ctr2" id="i3">108</td><td class="ctr1" id="j1">10</td><td class="ctr2" id="k1">10</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a8"><a href="ExtractImageScansController.html" class="el_class">ExtractImageScansController</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="392" alt="392"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="20" alt="20"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f11">15</td><td class="ctr2" id="g11">15</td><td class="ctr1" id="h7">88</td><td class="ctr2" id="i7">88</td><td class="ctr1" id="j6">5</td><td class="ctr2" id="k6">5</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a9"><a href="ExtractImagesController.html" class="el_class">ExtractImagesController</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="382" alt="382"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="32" alt="32"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">24</td><td class="ctr2" id="g4">24</td><td class="ctr1" id="h5">92</td><td class="ctr2" id="i5">92</td><td class="ctr1" id="j4">8</td><td class="ctr2" id="k4">8</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a3"><a href="AutoSplitPdfController.html" class="el_class">AutoSplitPdfController</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="359" alt="359"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="32" alt="32"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">21</td><td class="ctr2" id="g6">21</td><td class="ctr1" id="h6">92</td><td class="ctr2" id="i6">92</td><td class="ctr1" id="j7">5</td><td class="ctr2" id="k7">5</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a12"><a href="MetadataController.html" class="el_class">MetadataController</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="345" alt="345"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="42" alt="42"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f3">26</td><td class="ctr2" id="g3">26</td><td class="ctr1" id="h4">102</td><td class="ctr2" id="i4">102</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a15"><a href="PageNumbersController.html" class="el_class">PageNumbersController</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="317" alt="317"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="26" alt="26"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f9">18</td><td class="ctr2" id="g9">18</td><td class="ctr1" id="h8">83</td><td class="ctr2" id="i8">83</td><td class="ctr1" id="j15">2</td><td class="ctr2" id="k15">2</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a4"><a href="BlankPageController.html" class="el_class">BlankPageController</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="316" alt="316"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="28" alt="28"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f8">19</td><td class="ctr2" id="g8">19</td><td class="ctr1" id="h9">75</td><td class="ctr2" id="i9">75</td><td class="ctr1" id="j9">5</td><td class="ctr2" id="k9">5</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a2"><a href="AutoRenameController$1.html" class="el_class">AutoRenameController.new PDFTextStripper() {...}</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="239" alt="239"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="26" alt="26"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f10">18</td><td class="ctr2" id="g10">18</td><td class="ctr1" id="h11">47</td><td class="ctr2" id="i11">47</td><td class="ctr1" id="j10">5</td><td class="ctr2" id="k10">5</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a7"><a href="DecompressPdfController.html" class="el_class">DecompressPdfController</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="209" alt="209"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="24" alt="24"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f7">20</td><td class="ctr2" id="g7">20</td><td class="ctr1" id="h10">54</td><td class="ctr2" id="i10">54</td><td class="ctr1" id="j5">8</td><td class="ctr2" id="k5">8</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a11"><a href="FlattenController.html" class="el_class">FlattenController</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="115" alt="115"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f14">6</td><td class="ctr2" id="g14">6</td><td class="ctr1" id="h12">32</td><td class="ctr2" id="i12">32</td><td class="ctr1" id="j12">3</td><td class="ctr2" id="k12">3</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a16"><a href="PrintFileController.html" class="el_class">PrintFileController</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="113" alt="113"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f12">7</td><td class="ctr2" id="g12">7</td><td class="ctr1" id="h13">32</td><td class="ctr2" id="i13">32</td><td class="ctr1" id="j11">5</td><td class="ctr2" id="k11">5</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a20"><a href="ShowJavascript.html" class="el_class">ShowJavascript</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="84" alt="84"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="10" alt="10"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f13">7</td><td class="ctr2" id="g13">7</td><td class="ctr1" id="h14">26</td><td class="ctr2" id="i14">26</td><td class="ctr1" id="j16">2</td><td class="ctr2" id="k16">2</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a18"><a href="RepairController.html" class="el_class">RepairController</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="74" alt="74"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f18">2</td><td class="ctr2" id="g18">2</td><td class="ctr1" id="h15">22</td><td class="ctr2" id="i15">22</td><td class="ctr1" id="j17">2</td><td class="ctr2" id="k17">2</td><td class="ctr1" id="l15">1</td><td class="ctr2" id="m15">1</td></tr><tr><td id="a14"><a href="OverlayImageController.html" class="el_class">OverlayImageController</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="60" alt="60"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f16">3</td><td class="ctr2" id="g16">3</td><td class="ctr1" id="h16">19</td><td class="ctr2" id="i16">19</td><td class="ctr1" id="j13">3</td><td class="ctr2" id="k13">3</td><td class="ctr1" id="l16">1</td><td class="ctr2" id="m16">1</td></tr><tr><td id="a0"><a href="AutoRenameController.html" class="el_class">AutoRenameController</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="58" alt="58"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f15">5</td><td class="ctr2" id="g15">5</td><td class="ctr1" id="h17">15</td><td class="ctr2" id="i17">15</td><td class="ctr1" id="j14">3</td><td class="ctr2" id="k14">3</td><td class="ctr1" id="l17">1</td><td class="ctr2" id="m17">1</td></tr><tr><td id="a17"><a href="PrintFileController$1.html" class="el_class">PrintFileController.new Printable() {...}</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="38" alt="38"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f17">3</td><td class="ctr2" id="g17">3</td><td class="ctr1" id="h19">10</td><td class="ctr2" id="i19">10</td><td class="ctr1" id="j18">2</td><td class="ctr2" id="k18">2</td><td class="ctr1" id="l18">1</td><td class="ctr2" id="m18">1</td></tr><tr><td id="a19"><a href="ReplaceAndInvertColorController.html" class="el_class">ReplaceAndInvertColorController</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="35" alt="35"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">2</td><td class="ctr2" id="g19">2</td><td class="ctr1" id="h18">14</td><td class="ctr2" id="i18">14</td><td class="ctr1" id="j19">2</td><td class="ctr2" id="k19">2</td><td class="ctr1" id="l19">1</td><td class="ctr2" id="m19">1</td></tr><tr><td id="a6"><a href="CompressController$CompressionStats.html" class="el_class">CompressController.CompressionStats</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="24" alt="24"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">8</td><td class="ctr2" id="i20">8</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td><td class="ctr1" id="l20">1</td><td class="ctr2" id="m20">1</td></tr><tr><td id="a1"><a href="AutoRenameController$1$LineInfo.html" class="el_class">AutoRenameController.1.LineInfo</a></td><td class="bar" id="b21"/><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">4</td><td class="ctr2" id="i21">4</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td><td class="ctr1" id="l21">1</td><td class="ctr2" id="m21">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.misc/index.source.html b/testResult/html/stirling.software.SPDF.controller.api.misc/index.source.html
    new file mode 100644
    index 000000000..f160d52db
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.misc/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.misc</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.misc</span></div><h1>stirling.software.SPDF.controller.api.misc</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">6,771 of 6,771</td><td class="ctr2">0%</td><td class="bar">534 of 534</td><td class="ctr2">0%</td><td class="ctr1">402</td><td class="ctr2">402</td><td class="ctr1">1,584</td><td class="ctr2">1,584</td><td class="ctr1">123</td><td class="ctr2">123</td><td class="ctr1">22</td><td class="ctr2">22</td></tr></tfoot><tbody><tr><td id="a3"><a href="CompressController.java.html" class="el_source">CompressController.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,793" alt="1,793"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="173" alt="173"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">120</td><td class="ctr2" id="g0">120</td><td class="ctr1" id="h0">404</td><td class="ctr2" id="i0">404</td><td class="ctr1" id="j0">30</td><td class="ctr2" id="k0">30</td><td class="ctr1" id="l1">2</td><td class="ctr2" id="m1">2</td></tr><tr><td id="a7"><a href="FakeScanControllerWIP.java.html" class="el_source">FakeScanControllerWIP.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="59" height="10" title="886" alt="886"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="34" alt="34"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">26</td><td class="ctr2" id="g2">26</td><td class="ctr1" id="h1">139</td><td class="ctr2" id="i1">139</td><td class="ctr1" id="j2">9</td><td class="ctr2" id="k2">9</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a17"><a href="StampController.java.html" class="el_source">StampController.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="523" alt="523"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="46" alt="46"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">36</td><td class="ctr2" id="g1">36</td><td class="ctr1" id="h2">126</td><td class="ctr2" id="i2">126</td><td class="ctr1" id="j4">8</td><td class="ctr2" id="k4">8</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a10"><a href="OCRController.java.html" class="el_source">OCRController.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="421" alt="421"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="25" alt="25"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f6">23</td><td class="ctr2" id="g6">23</td><td class="ctr1" id="h3">108</td><td class="ctr2" id="i3">108</td><td class="ctr1" id="j1">10</td><td class="ctr2" id="k1">10</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a5"><a href="ExtractImageScansController.java.html" class="el_source">ExtractImageScansController.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="392" alt="392"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="20" alt="20"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f11">15</td><td class="ctr2" id="g11">15</td><td class="ctr1" id="h7">88</td><td class="ctr2" id="i7">88</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a6"><a href="ExtractImagesController.java.html" class="el_source">ExtractImagesController.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="382" alt="382"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="32" alt="32"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">24</td><td class="ctr2" id="g4">24</td><td class="ctr1" id="h5">92</td><td class="ctr2" id="i5">92</td><td class="ctr1" id="j5">8</td><td class="ctr2" id="k5">8</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a1"><a href="AutoSplitPdfController.java.html" class="el_source">AutoSplitPdfController.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="359" alt="359"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="32" alt="32"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f7">21</td><td class="ctr2" id="g7">21</td><td class="ctr1" id="h6">92</td><td class="ctr2" id="i6">92</td><td class="ctr1" id="j9">5</td><td class="ctr2" id="k9">5</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a9"><a href="MetadataController.java.html" class="el_source">MetadataController.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="345" alt="345"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="42" alt="42"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f3">26</td><td class="ctr2" id="g3">26</td><td class="ctr1" id="h4">102</td><td class="ctr2" id="i4">102</td><td class="ctr1" id="j10">5</td><td class="ctr2" id="k10">5</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a12"><a href="PageNumbersController.java.html" class="el_source">PageNumbersController.java</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="317" alt="317"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="26" alt="26"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f10">18</td><td class="ctr2" id="g10">18</td><td class="ctr1" id="h8">83</td><td class="ctr2" id="i8">83</td><td class="ctr1" id="j14">2</td><td class="ctr2" id="k14">2</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a2"><a href="BlankPageController.java.html" class="el_source">BlankPageController.java</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="316" alt="316"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="28" alt="28"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f9">19</td><td class="ctr2" id="g9">19</td><td class="ctr1" id="h9">75</td><td class="ctr2" id="i9">75</td><td class="ctr1" id="j11">5</td><td class="ctr2" id="k11">5</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a0"><a href="AutoRenameController.java.html" class="el_source">AutoRenameController.java</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="309" alt="309"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="30" alt="30"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f5">24</td><td class="ctr2" id="g5">24</td><td class="ctr1" id="h10">66</td><td class="ctr2" id="i10">66</td><td class="ctr1" id="j3">9</td><td class="ctr2" id="k3">9</td><td class="ctr1" id="l0">3</td><td class="ctr2" id="m0">3</td></tr><tr><td id="a4"><a href="DecompressPdfController.java.html" class="el_source">DecompressPdfController.java</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="209" alt="209"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="24" alt="24"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f8">20</td><td class="ctr2" id="g8">20</td><td class="ctr1" id="h11">54</td><td class="ctr2" id="i11">54</td><td class="ctr1" id="j6">8</td><td class="ctr2" id="k6">8</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a13"><a href="PrintFileController.java.html" class="el_source">PrintFileController.java</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="151" alt="151"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">10</td><td class="ctr2" id="g12">10</td><td class="ctr1" id="h12">42</td><td class="ctr2" id="i12">42</td><td class="ctr1" id="j7">7</td><td class="ctr2" id="k7">7</td><td class="ctr1" id="l2">2</td><td class="ctr2" id="m2">2</td></tr><tr><td id="a8"><a href="FlattenController.java.html" class="el_source">FlattenController.java</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="115" alt="115"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f14">6</td><td class="ctr2" id="g14">6</td><td class="ctr1" id="h13">32</td><td class="ctr2" id="i13">32</td><td class="ctr1" id="j12">3</td><td class="ctr2" id="k12">3</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a16"><a href="ShowJavascript.java.html" class="el_source">ShowJavascript.java</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="84" alt="84"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="10" alt="10"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f13">7</td><td class="ctr2" id="g13">7</td><td class="ctr1" id="h14">26</td><td class="ctr2" id="i14">26</td><td class="ctr1" id="j15">2</td><td class="ctr2" id="k15">2</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a14"><a href="RepairController.java.html" class="el_source">RepairController.java</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="74" alt="74"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f16">2</td><td class="ctr2" id="g16">2</td><td class="ctr1" id="h15">22</td><td class="ctr2" id="i15">22</td><td class="ctr1" id="j16">2</td><td class="ctr2" id="k16">2</td><td class="ctr1" id="l15">1</td><td class="ctr2" id="m15">1</td></tr><tr><td id="a11"><a href="OverlayImageController.java.html" class="el_source">OverlayImageController.java</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="60" alt="60"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f15">3</td><td class="ctr2" id="g15">3</td><td class="ctr1" id="h16">19</td><td class="ctr2" id="i16">19</td><td class="ctr1" id="j13">3</td><td class="ctr2" id="k13">3</td><td class="ctr1" id="l16">1</td><td class="ctr2" id="m16">1</td></tr><tr><td id="a15"><a href="ReplaceAndInvertColorController.java.html" class="el_source">ReplaceAndInvertColorController.java</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="35" alt="35"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">2</td><td class="ctr2" id="g17">2</td><td class="ctr1" id="h17">14</td><td class="ctr2" id="i17">14</td><td class="ctr1" id="j17">2</td><td class="ctr2" id="k17">2</td><td class="ctr1" id="l17">1</td><td class="ctr2" id="m17">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/ApiDocService.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/ApiDocService.html
    new file mode 100644
    index 000000000..c2deb020b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/ApiDocService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiDocService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">ApiDocService</span></div><h1>ApiDocService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">568 of 568</td><td class="ctr2">0%</td><td class="bar">32 of 32</td><td class="ctr2">0%</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">89</td><td class="ctr2">89</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a3"><a href="ApiDocService.java.html#L54" class="el_method">getExtensionTypes(boolean, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="362" alt="362"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">8</td><td class="ctr2" id="g0">8</td><td class="ctr1" id="h0">33</td><td class="ctr2" id="i0">33</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a7"><a href="ApiDocService.java.html#L107" class="el_method">loadApiDocumentation()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="65" alt="65"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h1">19</td><td class="ctr2" id="i1">19</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="ApiDocService.java.html#L151" class="el_method">isMultiInput(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="45" alt="45"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h2">12</td><td class="ctr2" id="i2">12</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="ApiDocService.java.html#L125" class="el_method">lambda$loadApiDocumentation$0(Map.Entry)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="29" alt="29"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h3">7</td><td class="ctr2" id="i3">7</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="ApiDocService.java.html#L140" class="el_method">isValidOperation(String, Map)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="23" alt="23"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h4">6</td><td class="ctr2" id="i4">6</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="ApiDocService.java.html#L33" class="el_method">ApiDocService(ServletContext, UserServiceInterface)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="19" alt="19"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="ApiDocService.java.html#L101" class="el_method">getApiKeyForUser()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="11" alt="11"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h7">2</td><td class="ctr2" id="i7">2</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a1"><a href="ApiDocService.java.html#L48" class="el_method">getApiDocsUrl()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="10" alt="10"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="ApiDocService.java.html#L30" class="el_method">static {...}</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/ApiDocService.java.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/ApiDocService.java.html
    new file mode 100644
    index 000000000..8cded9b01
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/ApiDocService.java.html
    @@ -0,0 +1,169 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiDocService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_source">ApiDocService.java</span></div><h1>ApiDocService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.pipeline;
    +
    +import java.util.Arrays;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.HttpEntity;
    +import org.springframework.http.HttpHeaders;
    +import org.springframework.http.HttpMethod;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.stereotype.Service;
    +import org.springframework.web.client.RestTemplate;
    +
    +import com.fasterxml.jackson.databind.JsonNode;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +import jakarta.servlet.ServletContext;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.SPDFApplication;
    +import stirling.software.SPDF.model.ApiEndpoint;
    +import stirling.software.SPDF.model.Role;
    +
    +@Service
    +<span class="nc" id="L30">@Slf4j</span>
    +public class ApiDocService {
    +
    +<span class="nc" id="L33">    private final Map&lt;String, ApiEndpoint&gt; apiDocumentation = new HashMap&lt;&gt;();</span>
    +
    +    private final ServletContext servletContext;
    +    private final UserServiceInterface userService;
    +<span class="nc" id="L37">    Map&lt;String, List&lt;String&gt;&gt; outputToFileTypes = new HashMap&lt;&gt;();</span>
    +    JsonNode apiDocsJsonRootNode;
    +
    +    public ApiDocService(
    +            ServletContext servletContext,
    +<span class="nc" id="L42">            @Autowired(required = false) UserServiceInterface userService) {</span>
    +<span class="nc" id="L43">        this.servletContext = servletContext;</span>
    +<span class="nc" id="L44">        this.userService = userService;</span>
    +<span class="nc" id="L45">    }</span>
    +
    +    private String getApiDocsUrl() {
    +<span class="nc" id="L48">        String contextPath = servletContext.getContextPath();</span>
    +<span class="nc" id="L49">        String port = SPDFApplication.getStaticPort();</span>
    +<span class="nc" id="L50">        return &quot;http://localhost:&quot; + port + contextPath + &quot;/v1/api-docs&quot;;</span>
    +    }
    +
    +    public List&lt;String&gt; getExtensionTypes(boolean output, String operationName) {
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">        if (outputToFileTypes.size() == 0) {</span>
    +<span class="nc" id="L55">            outputToFileTypes.put(&quot;PDF&quot;, Arrays.asList(&quot;pdf&quot;));</span>
    +<span class="nc" id="L56">            outputToFileTypes.put(</span>
    +                    &quot;IMAGE&quot;,
    +<span class="nc" id="L58">                    Arrays.asList(</span>
    +                            &quot;png&quot;, &quot;jpg&quot;, &quot;jpeg&quot;, &quot;gif&quot;, &quot;webp&quot;, &quot;bmp&quot;, &quot;tif&quot;, &quot;tiff&quot;, &quot;svg&quot;, &quot;psd&quot;,
    +                            &quot;ai&quot;, &quot;eps&quot;));
    +<span class="nc" id="L61">            outputToFileTypes.put(</span>
    +                    &quot;ZIP&quot;,
    +<span class="nc" id="L63">                    Arrays.asList(&quot;zip&quot;, &quot;rar&quot;, &quot;7z&quot;, &quot;tar&quot;, &quot;gz&quot;, &quot;bz2&quot;, &quot;xz&quot;, &quot;lz&quot;, &quot;lzma&quot;, &quot;z&quot;));</span>
    +<span class="nc" id="L64">            outputToFileTypes.put(&quot;WORD&quot;, Arrays.asList(&quot;doc&quot;, &quot;docx&quot;, &quot;odt&quot;, &quot;rtf&quot;));</span>
    +<span class="nc" id="L65">            outputToFileTypes.put(&quot;CSV&quot;, Arrays.asList(&quot;csv&quot;));</span>
    +<span class="nc" id="L66">            outputToFileTypes.put(&quot;JS&quot;, Arrays.asList(&quot;js&quot;, &quot;jsx&quot;));</span>
    +<span class="nc" id="L67">            outputToFileTypes.put(&quot;HTML&quot;, Arrays.asList(&quot;html&quot;, &quot;htm&quot;, &quot;xhtml&quot;));</span>
    +<span class="nc" id="L68">            outputToFileTypes.put(&quot;JSON&quot;, Arrays.asList(&quot;json&quot;));</span>
    +<span class="nc" id="L69">            outputToFileTypes.put(&quot;TXT&quot;, Arrays.asList(&quot;txt&quot;, &quot;text&quot;, &quot;md&quot;, &quot;markdown&quot;));</span>
    +<span class="nc" id="L70">            outputToFileTypes.put(&quot;PPT&quot;, Arrays.asList(&quot;ppt&quot;, &quot;pptx&quot;, &quot;odp&quot;));</span>
    +<span class="nc" id="L71">            outputToFileTypes.put(&quot;XML&quot;, Arrays.asList(&quot;xml&quot;, &quot;xsd&quot;, &quot;xsl&quot;));</span>
    +<span class="nc" id="L72">            outputToFileTypes.put(</span>
    +<span class="nc" id="L73">                    &quot;BOOK&quot;, Arrays.asList(&quot;epub&quot;, &quot;mobi&quot;, &quot;azw3&quot;, &quot;fb2&quot;, &quot;txt&quot;, &quot;docx&quot;));</span>
    +            // type.
    +        }
    +<span class="nc bnc" id="L76" title="All 4 branches missed.">        if (apiDocsJsonRootNode == null || apiDocumentation.size() == 0) {</span>
    +<span class="nc" id="L77">            loadApiDocumentation();</span>
    +        }
    +<span class="nc bnc" id="L79" title="All 2 branches missed.">        if (!apiDocumentation.containsKey(operationName)) {</span>
    +<span class="nc" id="L80">            return null;</span>
    +        }
    +<span class="nc" id="L82">        ApiEndpoint endpoint = apiDocumentation.get(operationName);</span>
    +<span class="nc" id="L83">        String description = endpoint.getDescription();</span>
    +<span class="nc" id="L84">        Pattern pattern = null;</span>
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">        if (output) {</span>
    +<span class="nc" id="L86">            pattern = Pattern.compile(&quot;Output:(\\w+)&quot;);</span>
    +        } else {
    +<span class="nc" id="L88">            pattern = Pattern.compile(&quot;Input:(\\w+)&quot;);</span>
    +        }
    +<span class="nc" id="L90">        Matcher matcher = pattern.matcher(description);</span>
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">        while (matcher.find()) {</span>
    +<span class="nc" id="L92">            String type = matcher.group(1).toUpperCase();</span>
    +<span class="nc bnc" id="L93" title="All 2 branches missed.">            if (outputToFileTypes.containsKey(type)) {</span>
    +<span class="nc" id="L94">                return outputToFileTypes.get(type);</span>
    +            }
    +<span class="nc" id="L96">        }</span>
    +<span class="nc" id="L97">        return null;</span>
    +    }
    +
    +    private String getApiKeyForUser() {
    +<span class="nc bnc" id="L101" title="All 2 branches missed.">        if (userService == null) return &quot;&quot;;</span>
    +<span class="nc" id="L102">        return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId());</span>
    +    }
    +
    +    // @EventListener(ApplicationReadyEvent.class)
    +    private synchronized void loadApiDocumentation() {
    +<span class="nc" id="L107">        String apiDocsJson = &quot;&quot;;</span>
    +        try {
    +<span class="nc" id="L109">            HttpHeaders headers = new HttpHeaders();</span>
    +<span class="nc" id="L110">            String apiKey = getApiKeyForUser();</span>
    +<span class="nc bnc" id="L111" title="All 2 branches missed.">            if (!apiKey.isEmpty()) {</span>
    +<span class="nc" id="L112">                headers.set(&quot;X-API-KEY&quot;, apiKey);</span>
    +            }
    +<span class="nc" id="L114">            HttpEntity&lt;String&gt; entity = new HttpEntity&lt;&gt;(headers);</span>
    +<span class="nc" id="L115">            RestTemplate restTemplate = new RestTemplate();</span>
    +<span class="nc" id="L116">            ResponseEntity&lt;String&gt; response =</span>
    +<span class="nc" id="L117">                    restTemplate.exchange(getApiDocsUrl(), HttpMethod.GET, entity, String.class);</span>
    +<span class="nc" id="L118">            apiDocsJson = response.getBody();</span>
    +<span class="nc" id="L119">            ObjectMapper mapper = new ObjectMapper();</span>
    +<span class="nc" id="L120">            apiDocsJsonRootNode = mapper.readTree(apiDocsJson);</span>
    +<span class="nc" id="L121">            JsonNode paths = apiDocsJsonRootNode.path(&quot;paths&quot;);</span>
    +<span class="nc" id="L122">            paths.fields()</span>
    +<span class="nc" id="L123">                    .forEachRemaining(</span>
    +                            entry -&gt; {
    +<span class="nc" id="L125">                                String path = entry.getKey();</span>
    +<span class="nc" id="L126">                                JsonNode pathNode = entry.getValue();</span>
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">                                if (pathNode.has(&quot;post&quot;)) {</span>
    +<span class="nc" id="L128">                                    JsonNode postNode = pathNode.get(&quot;post&quot;);</span>
    +<span class="nc" id="L129">                                    ApiEndpoint endpoint = new ApiEndpoint(path, postNode);</span>
    +<span class="nc" id="L130">                                    apiDocumentation.put(path, endpoint);</span>
    +                                }
    +<span class="nc" id="L132">                            });</span>
    +<span class="nc" id="L133">        } catch (Exception e) {</span>
    +            // Handle exceptions
    +<span class="nc" id="L135">            log.error(&quot;Error grabbing swagger doc, body result {}&quot;, apiDocsJson);</span>
    +<span class="nc" id="L136">        }</span>
    +<span class="nc" id="L137">    }</span>
    +
    +    public boolean isValidOperation(String operationName, Map&lt;String, Object&gt; parameters) {
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">        if (apiDocumentation.size() == 0) {</span>
    +<span class="nc" id="L141">            loadApiDocumentation();</span>
    +        }
    +<span class="nc bnc" id="L143" title="All 2 branches missed.">        if (!apiDocumentation.containsKey(operationName)) {</span>
    +<span class="nc" id="L144">            return false;</span>
    +        }
    +<span class="nc" id="L146">        ApiEndpoint endpoint = apiDocumentation.get(operationName);</span>
    +<span class="nc" id="L147">        return endpoint.areParametersValid(parameters);</span>
    +    }
    +
    +    public boolean isMultiInput(String operationName) {
    +<span class="nc bnc" id="L151" title="All 4 branches missed.">        if (apiDocsJsonRootNode == null || apiDocumentation.size() == 0) {</span>
    +<span class="nc" id="L152">            loadApiDocumentation();</span>
    +        }
    +<span class="nc bnc" id="L154" title="All 2 branches missed.">        if (!apiDocumentation.containsKey(operationName)) {</span>
    +<span class="nc" id="L155">            return false;</span>
    +        }
    +<span class="nc" id="L157">        ApiEndpoint endpoint = apiDocumentation.get(operationName);</span>
    +<span class="nc" id="L158">        String description = endpoint.getDescription();</span>
    +<span class="nc" id="L159">        Pattern pattern = Pattern.compile(&quot;Type:(\\w+)&quot;);</span>
    +<span class="nc" id="L160">        Matcher matcher = pattern.matcher(description);</span>
    +<span class="nc bnc" id="L161" title="All 2 branches missed.">        if (matcher.find()) {</span>
    +<span class="nc" id="L162">            String type = matcher.group(1);</span>
    +<span class="nc" id="L163">            return type.startsWith(&quot;MI&quot;);</span>
    +        }
    +<span class="nc" id="L165">        return false;</span>
    +    }
    +}
    +// Model class for API Endpoint
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineController.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineController.html
    new file mode 100644
    index 000000000..68697c3de
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">PipelineController</span></div><h1>PipelineController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">242 of 242</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">63</td><td class="ctr2">63</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="PipelineController.java.html#L57" class="el_method">handleData(HandleDataRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="226" alt="226"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">57</td><td class="ctr2" id="i0">57</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="PipelineController.java.html#L48" class="el_method">PipelineController(PipelineProcessor, ObjectMapper, PostHogService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="PipelineController.java.html#L37" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineController.java.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineController.java.html
    new file mode 100644
    index 000000000..9012ff862
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineController.java.html
    @@ -0,0 +1,134 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_source">PipelineController.java</span></div><h1>PipelineController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.pipeline;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.InputStream;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.springframework.core.io.Resource;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import com.fasterxml.jackson.core.JsonProcessingException;
    +import com.fasterxml.jackson.databind.JsonMappingException;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.PipelineConfig;
    +import stirling.software.SPDF.model.PipelineOperation;
    +import stirling.software.SPDF.model.PipelineResult;
    +import stirling.software.SPDF.model.api.HandleDataRequest;
    +import stirling.software.SPDF.service.PostHogService;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/pipeline&quot;)
    +<span class="nc" id="L37">@Slf4j</span>
    +@Tag(name = &quot;Pipeline&quot;, description = &quot;Pipeline APIs&quot;)
    +public class PipelineController {
    +
    +    private final PipelineProcessor processor;
    +
    +    private final ObjectMapper objectMapper;
    +
    +    private final PostHogService postHogService;
    +
    +    public PipelineController(
    +<span class="nc" id="L48">            PipelineProcessor processor, ObjectMapper objectMapper, PostHogService postHogService) {</span>
    +<span class="nc" id="L49">        this.processor = processor;</span>
    +<span class="nc" id="L50">        this.objectMapper = objectMapper;</span>
    +<span class="nc" id="L51">        this.postHogService = postHogService;</span>
    +<span class="nc" id="L52">    }</span>
    +
    +    @PostMapping(&quot;/handleData&quot;)
    +    public ResponseEntity&lt;byte[]&gt; handleData(@ModelAttribute HandleDataRequest request)
    +            throws JsonMappingException, JsonProcessingException {
    +<span class="nc" id="L57">        MultipartFile[] files = request.getFileInput();</span>
    +<span class="nc" id="L58">        String jsonString = request.getJson();</span>
    +<span class="nc bnc" id="L59" title="All 2 branches missed.">        if (files == null) {</span>
    +<span class="nc" id="L60">            return null;</span>
    +        }
    +<span class="nc" id="L62">        PipelineConfig config = objectMapper.readValue(jsonString, PipelineConfig.class);</span>
    +<span class="nc" id="L63">        log.info(&quot;Received POST request to /handleData with {} files&quot;, files.length);</span>
    +
    +<span class="nc" id="L65">        List&lt;String&gt; operationNames =</span>
    +<span class="nc" id="L66">                config.getOperations().stream().map(PipelineOperation::getOperation).toList();</span>
    +
    +<span class="nc" id="L68">        Map&lt;String, Object&gt; properties = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L69">        properties.put(&quot;operations&quot;, operationNames);</span>
    +<span class="nc" id="L70">        properties.put(&quot;fileCount&quot;, files.length);</span>
    +
    +<span class="nc" id="L72">        postHogService.captureEvent(&quot;pipeline_api_event&quot;, properties);</span>
    +
    +        try {
    +<span class="nc" id="L75">            List&lt;Resource&gt; inputFiles = processor.generateInputFiles(files);</span>
    +<span class="nc bnc" id="L76" title="All 4 branches missed.">            if (inputFiles == null || inputFiles.size() == 0) {</span>
    +<span class="nc" id="L77">                return null;</span>
    +            }
    +<span class="nc" id="L79">            PipelineResult result = processor.runPipelineAgainstFiles(inputFiles, config);</span>
    +<span class="nc" id="L80">            List&lt;Resource&gt; outputFiles = result.getOutputFiles();</span>
    +<span class="nc bnc" id="L81" title="All 4 branches missed.">            if (outputFiles != null &amp;&amp; outputFiles.size() == 1) {</span>
    +                // If there is only one file, return it directly
    +<span class="nc" id="L83">                Resource singleFile = outputFiles.get(0);</span>
    +<span class="nc" id="L84">                InputStream is = singleFile.getInputStream();</span>
    +<span class="nc" id="L85">                byte[] bytes = new byte[(int) singleFile.contentLength()];</span>
    +<span class="nc" id="L86">                is.read(bytes);</span>
    +<span class="nc" id="L87">                is.close();</span>
    +<span class="nc" id="L88">                log.info(&quot;Returning single file response...&quot;);</span>
    +<span class="nc" id="L89">                return WebResponseUtils.bytesToWebResponse(</span>
    +<span class="nc" id="L90">                        bytes, singleFile.getFilename(), MediaType.APPLICATION_OCTET_STREAM);</span>
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">            } else if (outputFiles == null) {</span>
    +<span class="nc" id="L92">                return null;</span>
    +            }
    +            // Create a ByteArrayOutputStream to hold the zip
    +<span class="nc" id="L95">            ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L96">            ZipOutputStream zipOut = new ZipOutputStream(baos);</span>
    +            // A map to keep track of filenames and their counts
    +<span class="nc" id="L98">            Map&lt;String, Integer&gt; filenameCount = new HashMap&lt;&gt;();</span>
    +            // Loop through each file and add it to the zip
    +<span class="nc bnc" id="L100" title="All 2 branches missed.">            for (Resource file : outputFiles) {</span>
    +<span class="nc" id="L101">                String originalFilename = file.getFilename();</span>
    +<span class="nc" id="L102">                String filename = originalFilename;</span>
    +                // Check if the filename already exists, and modify it if necessary
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">                if (filenameCount.containsKey(originalFilename)) {</span>
    +<span class="nc" id="L105">                    int count = filenameCount.get(originalFilename);</span>
    +<span class="nc" id="L106">                    String baseName = originalFilename.replaceAll(&quot;\\.[^.]*$&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L107">                    String extension = originalFilename.replaceAll(&quot;^.*\\.&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L108">                    filename = baseName + &quot;(&quot; + count + &quot;).&quot; + extension;</span>
    +<span class="nc" id="L109">                    filenameCount.put(originalFilename, count + 1);</span>
    +<span class="nc" id="L110">                } else {</span>
    +<span class="nc" id="L111">                    filenameCount.put(originalFilename, 1);</span>
    +                }
    +<span class="nc" id="L113">                ZipEntry zipEntry = new ZipEntry(filename);</span>
    +<span class="nc" id="L114">                zipOut.putNextEntry(zipEntry);</span>
    +                // Read the file into a byte array
    +<span class="nc" id="L116">                InputStream is = file.getInputStream();</span>
    +<span class="nc" id="L117">                byte[] bytes = new byte[(int) file.contentLength()];</span>
    +<span class="nc" id="L118">                is.read(bytes);</span>
    +                // Write the bytes of the file to the zip
    +<span class="nc" id="L120">                zipOut.write(bytes, 0, bytes.length);</span>
    +<span class="nc" id="L121">                zipOut.closeEntry();</span>
    +<span class="nc" id="L122">                is.close();</span>
    +<span class="nc" id="L123">            }</span>
    +<span class="nc" id="L124">            zipOut.close();</span>
    +<span class="nc" id="L125">            log.info(&quot;Returning zipped file response...&quot;);</span>
    +<span class="nc" id="L126">            return WebResponseUtils.boasToWebResponse(</span>
    +                    baos, &quot;output.zip&quot;, MediaType.APPLICATION_OCTET_STREAM);
    +<span class="nc" id="L128">        } catch (Exception e) {</span>
    +<span class="nc" id="L129">            log.error(&quot;Error handling data: &quot;, e);</span>
    +<span class="nc" id="L130">            return null;</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor$1.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor$1.html
    new file mode 100644
    index 000000000..c0eb1279b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineDirectoryProcessor.new SimpleFileVisitor() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">PipelineDirectoryProcessor.new SimpleFileVisitor() {...}</span></div><h1>PipelineDirectoryProcessor.new SimpleFileVisitor() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">38 of 38</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="PipelineDirectoryProcessor.java.html#L92" class="el_method">preVisitDirectory(Path, BasicFileAttributes)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="PipelineDirectoryProcessor.java.html#L86" class="el_method">{...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="PipelineDirectoryProcessor.java.html#L104" class="el_method">visitFileFailed(Path, IOException)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="7" alt="7"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor.html
    new file mode 100644
    index 000000000..bfe7719b1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineDirectoryProcessor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">PipelineDirectoryProcessor</span></div><h1>PipelineDirectoryProcessor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">797 of 797</td><td class="ctr2">0%</td><td class="bar">60 of 60</td><td class="ctr2">0%</td><td class="ctr1">54</td><td class="ctr2">54</td><td class="ctr1">203</td><td class="ctr2">203</td><td class="ctr1">24</td><td class="ctr2">24</td></tr></tfoot><tbody><tr><td id="a16"><a href="PipelineDirectoryProcessor.java.html#L244" class="el_method">prepareFilesForProcessing(File[], Path)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="96" alt="96"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h0">21</td><td class="ctr2" id="i0">21</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a17"><a href="PipelineDirectoryProcessor.java.html#L149" class="el_method">processPipelineOperations(Path, Path, Path, PipelineConfig)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="88" height="10" title="71" alt="71"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h1">16</td><td class="ctr2" id="i1">16</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a12"><a href="PipelineDirectoryProcessor.java.html#L340" class="el_method">moveAndRenameFiles(List, PipelineConfig, Path)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="55" alt="55"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h7">12</td><td class="ctr2" id="i7">12</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a20"><a href="PipelineDirectoryProcessor.java.html#L305" class="el_method">runPipelineAgainstFiles(List, PipelineConfig, Path, Path)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="53" alt="53"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">4</td><td class="ctr2" id="g3">4</td><td class="ctr1" id="h3">15</td><td class="ctr2" id="i3">15</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a8"><a href="PipelineDirectoryProcessor.java.html#L191" class="el_method">lambda$collectFilesForProcessing$1(Path, boolean, List, Path)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="63" height="10" title="51" alt="51"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h4">15</td><td class="ctr2" id="i4">15</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="PipelineDirectoryProcessor.java.html#L178" class="el_method">collectFilesForProcessing(Path, Path, PipelineOperation)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="62" height="10" title="50" alt="50"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h2">16</td><td class="ctr2" id="i2">16</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a21"><a href="PipelineDirectoryProcessor.java.html#L72" class="el_method">scanFolders()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="45" alt="45"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h6">13</td><td class="ctr2" id="i6">13</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="PipelineDirectoryProcessor.java.html#L356" class="el_method">createOutputFileName(Resource, PipelineConfig)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="55" height="10" title="44" alt="44"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h5">14</td><td class="ctr2" id="i5">14</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a14"><a href="PipelineDirectoryProcessor.java.html#L326" class="el_method">moveToErrorDirectory(List, Path)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="52" height="10" title="42" alt="42"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h10">9</td><td class="ctr2" id="i10">9</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a13"><a href="PipelineDirectoryProcessor.java.html#L394" class="el_method">moveFilesBack(List, Path)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="37" alt="37"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h8">10</td><td class="ctr2" id="i8">10</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a7"><a href="PipelineDirectoryProcessor.java.html#L114" class="el_method">handleDirectory(Path)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="35" alt="35"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h9">10</td><td class="ctr2" id="i9">10</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a5"><a href="PipelineDirectoryProcessor.java.html#L377" class="el_method">determineOutputPath(PipelineConfig, Path)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="34" alt="34"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h13">6</td><td class="ctr2" id="i13">6</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a15"><a href="PipelineDirectoryProcessor.java.html#L60" class="el_method">PipelineDirectoryProcessor(ObjectMapper, ApiDocService, PipelineProcessor, FileMonitor, PostHogService, RuntimePathConfig)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="26" alt="26"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h11">9</td><td class="ctr2" id="i11">9</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a19"><a href="PipelineDirectoryProcessor.java.html#L280" class="el_method">resolveUniqueFilePath(Path, String)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="25" alt="25"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h12">8</td><td class="ctr2" id="i12">8</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a4"><a href="PipelineDirectoryProcessor.java.html#L387" class="el_method">deleteOriginalFiles(List, Path)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="23" alt="23"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f11">2</td><td class="ctr2" id="g11">2</td><td class="ctr1" id="h14">5</td><td class="ctr2" id="i14">5</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a0"><a href="PipelineDirectoryProcessor.java.html#L291" class="el_method">appendSuffixToFileName(String, String)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="21" alt="21"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">2</td><td class="ctr2" id="g12">2</td><td class="ctr1" id="h15">5</td><td class="ctr2" id="i15">5</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a3"><a href="PipelineDirectoryProcessor.java.html#L127" class="el_method">createProcessingDirectory(Path)</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="20" alt="20"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f13">2</td><td class="ctr2" id="g13">2</td><td class="ctr1" id="h16">5</td><td class="ctr2" id="i16">5</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a18"><a href="PipelineDirectoryProcessor.java.html#L142" class="el_method">readAndParseJson(Path)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="18" alt="18"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h18">3</td><td class="ctr2" id="i18">3</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a23"><a href="PipelineDirectoryProcessor.java.html#L170" class="el_method">validateOperation(PipelineOperation)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="16" alt="16"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f14">2</td><td class="ctr2" id="g14">2</td><td class="ctr1" id="h19">3</td><td class="ctr2" id="i19">3</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a9"><a href="PipelineDirectoryProcessor.java.html#L223" class="el_method">lambda$collectFilesForProcessing$2(Path)</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="13" alt="13"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f15">2</td><td class="ctr2" id="g15">2</td><td class="ctr1" id="h17">5</td><td class="ctr2" id="i17">5</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a6"><a href="PipelineDirectoryProcessor.java.html#L136" class="el_method">findJsonFile(Path)</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="10" alt="10"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">2</td><td class="ctr2" id="i20">2</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a11"><a href="PipelineDirectoryProcessor.java.html#L137" class="el_method">lambda$findJsonFile$0(Path)</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="5" alt="5"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">1</td><td class="ctr2" id="i21">1</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a22"><a href="PipelineDirectoryProcessor.java.html#L43" class="el_method">static {...}</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h22">1</td><td class="ctr2" id="i22">1</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a10"><a href="PipelineDirectoryProcessor.java.html#L233" class="el_method">lambda$collectFilesForProcessing$3(int)</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="3" alt="3"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">1</td><td class="ctr2" id="i23">1</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor.java.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor.java.html
    new file mode 100644
    index 000000000..5f05f0e69
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineDirectoryProcessor.java.html
    @@ -0,0 +1,407 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineDirectoryProcessor.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_source">PipelineDirectoryProcessor.java</span></div><h1>PipelineDirectoryProcessor.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.pipeline;
    +
    +import java.io.File;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.io.OutputStream;
    +import java.nio.charset.StandardCharsets;
    +import java.nio.file.FileSystemException;
    +import java.nio.file.FileVisitResult;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.nio.file.SimpleFileVisitor;
    +import java.nio.file.StandardCopyOption;
    +import java.nio.file.attribute.BasicFileAttributes;
    +import java.time.LocalDate;
    +import java.time.LocalTime;
    +import java.time.format.DateTimeFormatter;
    +import java.util.ArrayList;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Optional;
    +import java.util.stream.Stream;
    +
    +import org.springframework.core.io.ByteArrayResource;
    +import org.springframework.core.io.Resource;
    +import org.springframework.scheduling.annotation.Scheduled;
    +import org.springframework.stereotype.Service;
    +
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.RuntimePathConfig;
    +import stirling.software.SPDF.model.PipelineConfig;
    +import stirling.software.SPDF.model.PipelineOperation;
    +import stirling.software.SPDF.model.PipelineResult;
    +import stirling.software.SPDF.service.PostHogService;
    +import stirling.software.SPDF.utils.FileMonitor;
    +
    +@Service
    +<span class="nc" id="L43">@Slf4j</span>
    +public class PipelineDirectoryProcessor {
    +
    +    private final ObjectMapper objectMapper;
    +    private final ApiDocService apiDocService;
    +    private final PipelineProcessor processor;
    +    private final FileMonitor fileMonitor;
    +    private final PostHogService postHogService;
    +    private final String watchedFoldersDir;
    +    private final String finishedFoldersDir;
    +
    +    public PipelineDirectoryProcessor(
    +            ObjectMapper objectMapper,
    +            ApiDocService apiDocService,
    +            PipelineProcessor processor,
    +            FileMonitor fileMonitor,
    +            PostHogService postHogService,
    +<span class="nc" id="L60">            RuntimePathConfig runtimePathConfig) {</span>
    +<span class="nc" id="L61">        this.objectMapper = objectMapper;</span>
    +<span class="nc" id="L62">        this.apiDocService = apiDocService;</span>
    +<span class="nc" id="L63">        this.processor = processor;</span>
    +<span class="nc" id="L64">        this.fileMonitor = fileMonitor;</span>
    +<span class="nc" id="L65">        this.postHogService = postHogService;</span>
    +<span class="nc" id="L66">        this.watchedFoldersDir = runtimePathConfig.getPipelineWatchedFoldersPath();</span>
    +<span class="nc" id="L67">        this.finishedFoldersDir = runtimePathConfig.getPipelineFinishedFoldersPath();</span>
    +<span class="nc" id="L68">    }</span>
    +
    +    @Scheduled(fixedRate = 60000)
    +    public void scanFolders() {
    +<span class="nc" id="L72">        Path watchedFolderPath = Paths.get(watchedFoldersDir).toAbsolutePath();</span>
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">        if (!Files.exists(watchedFolderPath)) {</span>
    +            try {
    +<span class="nc" id="L75">                Files.createDirectories(watchedFolderPath);</span>
    +<span class="nc" id="L76">                log.info(&quot;Created directory: {}&quot;, watchedFolderPath);</span>
    +<span class="nc" id="L77">            } catch (IOException e) {</span>
    +<span class="nc" id="L78">                log.error(&quot;Error creating directory: {}&quot;, watchedFolderPath, e);</span>
    +<span class="nc" id="L79">                return;</span>
    +<span class="nc" id="L80">            }</span>
    +        }
    +
    +        try {
    +<span class="nc" id="L84">            Files.walkFileTree(</span>
    +                    watchedFolderPath,
    +<span class="nc" id="L86">                    new SimpleFileVisitor&lt;&gt;() {</span>
    +                        @Override
    +                        public FileVisitResult preVisitDirectory(
    +                                Path dir, BasicFileAttributes attrs) {
    +                            try {
    +                                // Skip root directory and &quot;processing&quot; subdirectories
    +<span class="nc bnc" id="L92" title="All 4 branches missed.">                                if (!dir.equals(watchedFolderPath) &amp;&amp; !dir.endsWith(&quot;processing&quot;)) {</span>
    +<span class="nc" id="L93">                                    handleDirectory(dir);</span>
    +                                }
    +<span class="nc" id="L95">                            } catch (Exception e) {</span>
    +<span class="nc" id="L96">                                log.error(&quot;Error handling directory: {}&quot;, dir, e);</span>
    +<span class="nc" id="L97">                            }</span>
    +<span class="nc" id="L98">                            return FileVisitResult.CONTINUE;</span>
    +                        }
    +
    +                        @Override
    +                        public FileVisitResult visitFileFailed(Path path, IOException exc) {
    +                            // Handle broken symlinks or inaccessible directories
    +<span class="nc" id="L104">                            log.error(&quot;Error accessing path: {}&quot;, path, exc);</span>
    +<span class="nc" id="L105">                            return FileVisitResult.CONTINUE;</span>
    +                        }
    +                    });
    +<span class="nc" id="L108">        } catch (IOException e) {</span>
    +<span class="nc" id="L109">            log.error(&quot;Error walking through directory: {}&quot;, watchedFolderPath, e);</span>
    +<span class="nc" id="L110">        }</span>
    +<span class="nc" id="L111">    }</span>
    +
    +    public void handleDirectory(Path dir) throws IOException {
    +<span class="nc" id="L114">        log.info(&quot;Handling directory: {}&quot;, dir);</span>
    +<span class="nc" id="L115">        Path processingDir = createProcessingDirectory(dir);</span>
    +<span class="nc" id="L116">        Optional&lt;Path&gt; jsonFileOptional = findJsonFile(dir);</span>
    +<span class="nc bnc" id="L117" title="All 2 branches missed.">        if (!jsonFileOptional.isPresent()) {</span>
    +<span class="nc" id="L118">            log.warn(&quot;No .JSON settings file found. No processing will happen for dir {}.&quot;, dir);</span>
    +<span class="nc" id="L119">            return;</span>
    +        }
    +<span class="nc" id="L121">        Path jsonFile = jsonFileOptional.get();</span>
    +<span class="nc" id="L122">        PipelineConfig config = readAndParseJson(jsonFile);</span>
    +<span class="nc" id="L123">        processPipelineOperations(dir, processingDir, jsonFile, config);</span>
    +<span class="nc" id="L124">    }</span>
    +
    +    private Path createProcessingDirectory(Path dir) throws IOException {
    +<span class="nc" id="L127">        Path processingDir = dir.resolve(&quot;processing&quot;);</span>
    +<span class="nc bnc" id="L128" title="All 2 branches missed.">        if (!Files.exists(processingDir)) {</span>
    +<span class="nc" id="L129">            Files.createDirectory(processingDir);</span>
    +<span class="nc" id="L130">            log.info(&quot;Created processing directory: {}&quot;, processingDir);</span>
    +        }
    +<span class="nc" id="L132">        return processingDir;</span>
    +    }
    +
    +    private Optional&lt;Path&gt; findJsonFile(Path dir) throws IOException {
    +<span class="nc" id="L136">        try (Stream&lt;Path&gt; paths = Files.list(dir)) {</span>
    +<span class="nc" id="L137">            return paths.filter(file -&gt; file.toString().endsWith(&quot;.json&quot;)).findFirst();</span>
    +        }
    +    }
    +
    +    private PipelineConfig readAndParseJson(Path jsonFile) throws IOException {
    +<span class="nc" id="L142">        String jsonString = new String(Files.readAllBytes(jsonFile), StandardCharsets.UTF_8);</span>
    +<span class="nc" id="L143">        log.debug(&quot;Reading JSON file: {}&quot;, jsonFile);</span>
    +<span class="nc" id="L144">        return objectMapper.readValue(jsonString, PipelineConfig.class);</span>
    +    }
    +
    +    private void processPipelineOperations(
    +            Path dir, Path processingDir, Path jsonFile, PipelineConfig config) throws IOException {
    +<span class="nc bnc" id="L149" title="All 2 branches missed.">        for (PipelineOperation operation : config.getOperations()) {</span>
    +<span class="nc" id="L150">            validateOperation(operation);</span>
    +<span class="nc" id="L151">            File[] files = collectFilesForProcessing(dir, jsonFile, operation);</span>
    +<span class="nc bnc" id="L152" title="All 4 branches missed.">            if (files == null || files.length == 0) {</span>
    +<span class="nc" id="L153">                log.debug(&quot;No files detected for {} &quot;, dir);</span>
    +<span class="nc" id="L154">                return;</span>
    +            }
    +
    +<span class="nc" id="L157">            List&lt;String&gt; operationNames =</span>
    +<span class="nc" id="L158">                    config.getOperations().stream().map(PipelineOperation::getOperation).toList();</span>
    +<span class="nc" id="L159">            Map&lt;String, Object&gt; properties = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L160">            properties.put(&quot;operations&quot;, operationNames);</span>
    +<span class="nc" id="L161">            properties.put(&quot;fileCount&quot;, files.length);</span>
    +<span class="nc" id="L162">            postHogService.captureEvent(&quot;pipeline_directory_event&quot;, properties);</span>
    +
    +<span class="nc" id="L164">            List&lt;File&gt; filesToProcess = prepareFilesForProcessing(files, processingDir);</span>
    +<span class="nc" id="L165">            runPipelineAgainstFiles(filesToProcess, config, dir, processingDir);</span>
    +<span class="nc" id="L166">        }</span>
    +<span class="nc" id="L167">    }</span>
    +
    +    private void validateOperation(PipelineOperation operation) throws IOException {
    +<span class="nc bnc" id="L170" title="All 2 branches missed.">        if (!apiDocService.isValidOperation(operation.getOperation(), operation.getParameters())) {</span>
    +<span class="nc" id="L171">            throw new IOException(&quot;Invalid operation: &quot; + operation.getOperation());</span>
    +        }
    +<span class="nc" id="L173">    }</span>
    +
    +    private File[] collectFilesForProcessing(Path dir, Path jsonFile, PipelineOperation operation)
    +            throws IOException {
    +
    +<span class="nc" id="L178">        List&lt;String&gt; inputExtensions =</span>
    +<span class="nc" id="L179">                apiDocService.getExtensionTypes(false, operation.getOperation());</span>
    +<span class="nc" id="L180">        log.info(</span>
    +                &quot;Allowed extensions for operation {}: {}&quot;,
    +<span class="nc" id="L182">                operation.getOperation(),</span>
    +                inputExtensions);
    +
    +<span class="nc" id="L185">        boolean allowAllFiles = inputExtensions.contains(&quot;ALL&quot;);</span>
    +
    +<span class="nc" id="L187">        try (Stream&lt;Path&gt; paths = Files.list(dir)) {</span>
    +<span class="nc" id="L188">            File[] files =</span>
    +<span class="nc" id="L189">                    paths.filter(</span>
    +                                    path -&gt; {
    +<span class="nc bnc" id="L191" title="All 2 branches missed.">                                        if (Files.isDirectory(path)) {</span>
    +<span class="nc" id="L192">                                            return false;</span>
    +                                        }
    +<span class="nc bnc" id="L194" title="All 2 branches missed.">                                        if (path.equals(jsonFile)) {</span>
    +<span class="nc" id="L195">                                            return false;</span>
    +                                        }
    +
    +                                        // Get file extension
    +<span class="nc" id="L199">                                        String filename = path.getFileName().toString();</span>
    +                                        String extension =
    +<span class="nc bnc" id="L201" title="All 2 branches missed.">                                                filename.contains(&quot;.&quot;)</span>
    +<span class="nc" id="L202">                                                        ? filename.substring(</span>
    +<span class="nc" id="L203">                                                                        filename.lastIndexOf(&quot;.&quot;)</span>
    +                                                                                + 1)
    +<span class="nc" id="L205">                                                                .toLowerCase()</span>
    +<span class="nc" id="L206">                                                        : &quot;&quot;;</span>
    +
    +                                        // Check against allowed extensions
    +<span class="nc bnc" id="L209" title="All 2 branches missed.">                                        boolean isAllowed =</span>
    +                                                allowAllFiles
    +<span class="nc bnc" id="L211" title="All 2 branches missed.">                                                        || inputExtensions.contains(extension);</span>
    +<span class="nc bnc" id="L212" title="All 2 branches missed.">                                        if (!isAllowed) {</span>
    +<span class="nc" id="L213">                                            log.info(</span>
    +                                                    &quot;Skipping file with unsupported extension: {} ({})&quot;,
    +                                                    filename,
    +                                                    extension);
    +                                        }
    +<span class="nc" id="L218">                                        return isAllowed;</span>
    +                                    })
    +<span class="nc" id="L220">                            .map(Path::toAbsolutePath)</span>
    +<span class="nc" id="L221">                            .filter(</span>
    +                                    path -&gt; {
    +<span class="nc" id="L223">                                        boolean isReady =</span>
    +<span class="nc" id="L224">                                                fileMonitor.isFileReadyForProcessing(path);</span>
    +<span class="nc bnc" id="L225" title="All 2 branches missed.">                                        if (!isReady) {</span>
    +<span class="nc" id="L226">                                            log.info(</span>
    +                                                    &quot;File not ready for processing (locked/created last 5s): {}&quot;,
    +                                                    path);
    +                                        }
    +<span class="nc" id="L230">                                        return isReady;</span>
    +                                    })
    +<span class="nc" id="L232">                            .map(Path::toFile)</span>
    +<span class="nc" id="L233">                            .toArray(File[]::new);</span>
    +<span class="nc" id="L234">            log.info(</span>
    +                    &quot;Collected {} files for processing for {}&quot;,
    +<span class="nc" id="L236">                    files.length,</span>
    +<span class="nc" id="L237">                    dir.toAbsolutePath().toString());</span>
    +<span class="nc" id="L238">            return files;</span>
    +        }
    +    }
    +
    +    private List&lt;File&gt; prepareFilesForProcessing(File[] files, Path processingDir)
    +            throws IOException {
    +<span class="nc" id="L244">        List&lt;File&gt; filesToProcess = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L245" title="All 2 branches missed.">        for (File file : files) {</span>
    +<span class="nc" id="L246">            Path targetPath = resolveUniqueFilePath(processingDir, file.getName());</span>
    +
    +            // Retry with exponential backoff
    +<span class="nc" id="L249">            int maxRetries = 3;</span>
    +<span class="nc" id="L250">            int retryDelayMs = 500;</span>
    +<span class="nc" id="L251">            boolean moved = false;</span>
    +
    +<span class="nc bnc" id="L253" title="All 2 branches missed.">            for (int attempt = 1; attempt &lt;= maxRetries; attempt++) {</span>
    +                try {
    +<span class="nc" id="L255">                    Files.move(file.toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING);</span>
    +<span class="nc" id="L256">                    moved = true;</span>
    +<span class="nc" id="L257">                    break;</span>
    +<span class="nc" id="L258">                } catch (FileSystemException e) {</span>
    +<span class="nc bnc" id="L259" title="All 2 branches missed.">                    if (attempt &lt; maxRetries) {</span>
    +<span class="nc" id="L260">                        log.info(&quot;File move failed (attempt {}), retrying...&quot;, attempt);</span>
    +                        try {
    +<span class="nc" id="L262">                            Thread.sleep(retryDelayMs * (int) Math.pow(2, attempt - 1));</span>
    +<span class="nc" id="L263">                        } catch (InterruptedException e1) {</span>
    +<span class="nc" id="L264">                            log.error(&quot;prepareFilesForProcessing failure&quot;, e);</span>
    +<span class="nc" id="L265">                        }</span>
    +                    }
    +                }
    +            }
    +
    +<span class="nc bnc" id="L270" title="All 2 branches missed.">            if (moved) {</span>
    +<span class="nc" id="L271">                filesToProcess.add(targetPath.toFile());</span>
    +            } else {
    +<span class="nc" id="L273">                log.error(&quot;Failed to move file after {} attempts: {}&quot;, maxRetries, file.getName());</span>
    +            }
    +        }
    +<span class="nc" id="L276">        return filesToProcess;</span>
    +    }
    +
    +    private Path resolveUniqueFilePath(Path directory, String originalFileName) {
    +<span class="nc" id="L280">        Path filePath = directory.resolve(originalFileName);</span>
    +<span class="nc" id="L281">        int counter = 1;</span>
    +<span class="nc bnc" id="L282" title="All 2 branches missed.">        while (Files.exists(filePath)) {</span>
    +<span class="nc" id="L283">            String newName = appendSuffixToFileName(originalFileName, &quot;(&quot; + counter + &quot;)&quot;);</span>
    +<span class="nc" id="L284">            filePath = directory.resolve(newName);</span>
    +<span class="nc" id="L285">            counter++;</span>
    +<span class="nc" id="L286">        }</span>
    +<span class="nc" id="L287">        return filePath;</span>
    +    }
    +
    +    private String appendSuffixToFileName(String originalFileName, String suffix) {
    +<span class="nc" id="L291">        int dotIndex = originalFileName.lastIndexOf('.');</span>
    +<span class="nc bnc" id="L292" title="All 2 branches missed.">        if (dotIndex == -1) {</span>
    +<span class="nc" id="L293">            return originalFileName + suffix;</span>
    +        } else {
    +<span class="nc" id="L295">            return originalFileName.substring(0, dotIndex)</span>
    +                    + suffix
    +<span class="nc" id="L297">                    + originalFileName.substring(dotIndex);</span>
    +        }
    +    }
    +
    +    private void runPipelineAgainstFiles(
    +            List&lt;File&gt; filesToProcess, PipelineConfig config, Path dir, Path processingDir)
    +            throws IOException {
    +        try {
    +<span class="nc" id="L305">            List&lt;Resource&gt; inputFiles =</span>
    +<span class="nc" id="L306">                    processor.generateInputFiles(filesToProcess.toArray(new File[0]));</span>
    +<span class="nc bnc" id="L307" title="All 4 branches missed.">            if (inputFiles == null || inputFiles.isEmpty()) {</span>
    +<span class="nc" id="L308">                return;</span>
    +            }
    +<span class="nc" id="L310">            PipelineResult result = processor.runPipelineAgainstFiles(inputFiles, config);</span>
    +
    +<span class="nc bnc" id="L312" title="All 2 branches missed.">            if (result.isHasErrors()) {</span>
    +<span class="nc" id="L313">                log.error(&quot;Errors occurred during processing, retaining original files&quot;);</span>
    +<span class="nc" id="L314">                moveToErrorDirectory(filesToProcess, dir);</span>
    +            } else {
    +<span class="nc" id="L316">                moveAndRenameFiles(result.getOutputFiles(), config, dir);</span>
    +<span class="nc" id="L317">                deleteOriginalFiles(filesToProcess, processingDir);</span>
    +            }
    +<span class="nc" id="L319">        } catch (Exception e) {</span>
    +<span class="nc" id="L320">            log.error(&quot;Error during processing&quot;, e);</span>
    +<span class="nc" id="L321">            moveFilesBack(filesToProcess, processingDir);</span>
    +<span class="nc" id="L322">        }</span>
    +<span class="nc" id="L323">    }</span>
    +
    +    private void moveToErrorDirectory(List&lt;File&gt; files, Path originalDir) throws IOException {
    +<span class="nc" id="L326">        Path errorDir = originalDir.resolve(&quot;error&quot;);</span>
    +<span class="nc bnc" id="L327" title="All 2 branches missed.">        if (!Files.exists(errorDir)) {</span>
    +<span class="nc" id="L328">            Files.createDirectories(errorDir);</span>
    +        }
    +
    +<span class="nc bnc" id="L331" title="All 2 branches missed.">        for (File file : files) {</span>
    +<span class="nc" id="L332">            Path target = errorDir.resolve(file.getName());</span>
    +<span class="nc" id="L333">            Files.move(file.toPath(), target);</span>
    +<span class="nc" id="L334">            log.info(&quot;Moved failed file to error directory for investigation: {}&quot;, target);</span>
    +<span class="nc" id="L335">        }</span>
    +<span class="nc" id="L336">    }</span>
    +
    +    private void moveAndRenameFiles(List&lt;Resource&gt; resources, PipelineConfig config, Path dir)
    +            throws IOException {
    +<span class="nc bnc" id="L340" title="All 2 branches missed.">        for (Resource resource : resources) {</span>
    +<span class="nc" id="L341">            String outputFileName = createOutputFileName(resource, config);</span>
    +<span class="nc" id="L342">            Path outputPath = determineOutputPath(config, dir);</span>
    +<span class="nc bnc" id="L343" title="All 2 branches missed.">            if (!Files.exists(outputPath)) {</span>
    +<span class="nc" id="L344">                Files.createDirectories(outputPath);</span>
    +<span class="nc" id="L345">                log.info(&quot;Created directory: {}&quot;, outputPath);</span>
    +            }
    +<span class="nc" id="L347">            Path outputFile = outputPath.resolve(outputFileName);</span>
    +<span class="nc" id="L348">            try (OutputStream os = new FileOutputStream(outputFile.toFile())) {</span>
    +<span class="nc" id="L349">                os.write(((ByteArrayResource) resource).getByteArray());</span>
    +            }
    +<span class="nc" id="L351">            log.info(&quot;File moved and renamed to {}&quot;, outputFile);</span>
    +<span class="nc" id="L352">        }</span>
    +<span class="nc" id="L353">    }</span>
    +
    +    private String createOutputFileName(Resource resource, PipelineConfig config) {
    +<span class="nc" id="L356">        String resourceName = resource.getFilename();</span>
    +<span class="nc" id="L357">        String baseName = resourceName.substring(0, resourceName.lastIndexOf('.'));</span>
    +<span class="nc" id="L358">        String extension = resourceName.substring(resourceName.lastIndexOf('.') + 1);</span>
    +<span class="nc" id="L359">        String outputFileName =</span>
    +<span class="nc" id="L360">                config.getOutputPattern()</span>
    +<span class="nc" id="L361">                                .replace(&quot;{filename}&quot;, baseName)</span>
    +<span class="nc" id="L362">                                .replace(&quot;{pipelineName}&quot;, config.getName())</span>
    +<span class="nc" id="L363">                                .replace(</span>
    +                                        &quot;{date}&quot;,
    +<span class="nc" id="L365">                                        LocalDate.now()</span>
    +<span class="nc" id="L366">                                                .format(DateTimeFormatter.ofPattern(&quot;yyyyMMdd&quot;)))</span>
    +<span class="nc" id="L367">                                .replace(</span>
    +                                        &quot;{time}&quot;,
    +<span class="nc" id="L369">                                        LocalTime.now()</span>
    +<span class="nc" id="L370">                                                .format(DateTimeFormatter.ofPattern(&quot;HHmmss&quot;)))</span>
    +                        + &quot;.&quot;
    +                        + extension;
    +<span class="nc" id="L373">        return outputFileName;</span>
    +    }
    +
    +    private Path determineOutputPath(PipelineConfig config, Path dir) {
    +<span class="nc" id="L377">        String outputDir =</span>
    +<span class="nc" id="L378">                config.getOutputDir()</span>
    +<span class="nc" id="L379">                        .replace(&quot;{outputFolder}&quot;, finishedFoldersDir)</span>
    +<span class="nc" id="L380">                        .replace(&quot;{folderName}&quot;, dir.toString())</span>
    +<span class="nc" id="L381">                        .replaceAll(&quot;\\\\?watchedFolders&quot;, &quot;&quot;);</span>
    +<span class="nc bnc" id="L382" title="All 2 branches missed.">        return Paths.get(outputDir).isAbsolute() ? Paths.get(outputDir) : Paths.get(&quot;.&quot;, outputDir);</span>
    +    }
    +
    +    private void deleteOriginalFiles(List&lt;File&gt; filesToProcess, Path processingDir)
    +            throws IOException {
    +<span class="nc bnc" id="L387" title="All 2 branches missed.">        for (File file : filesToProcess) {</span>
    +<span class="nc" id="L388">            Files.deleteIfExists(processingDir.resolve(file.getName()));</span>
    +<span class="nc" id="L389">            log.info(&quot;Deleted original file: {}&quot;, file.getName());</span>
    +<span class="nc" id="L390">        }</span>
    +<span class="nc" id="L391">    }</span>
    +
    +    private void moveFilesBack(List&lt;File&gt; filesToProcess, Path processingDir) {
    +<span class="nc bnc" id="L394" title="All 2 branches missed.">        for (File file : filesToProcess) {</span>
    +            try {
    +<span class="nc" id="L396">                Files.move(processingDir.resolve(file.getName()), file.toPath());</span>
    +<span class="nc" id="L397">                log.info(</span>
    +                        &quot;Moved file back to original location: {} , {}&quot;,
    +<span class="nc" id="L399">                        file.toPath(),</span>
    +<span class="nc" id="L400">                        file.getName());</span>
    +<span class="nc" id="L401">            } catch (IOException e) {</span>
    +<span class="nc" id="L402">                log.error(&quot;Error moving file back to original location: {}&quot;, file.getName(), e);</span>
    +<span class="nc" id="L403">            }</span>
    +<span class="nc" id="L404">        }</span>
    +<span class="nc" id="L405">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$1.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$1.html
    new file mode 100644
    index 000000000..635013b81
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineProcessor.new ByteArrayResource() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">PipelineProcessor.new ByteArrayResource() {...}</span></div><h1>PipelineProcessor.new ByteArrayResource() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">13 of 13</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="PipelineProcessor.java.html#L253" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PipelineProcessor.java.html#L257" class="el_method">getFilename()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$2.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$2.html
    new file mode 100644
    index 000000000..c3f3a1e86
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$2.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineProcessor.new ByteArrayResource() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">PipelineProcessor.new ByteArrayResource() {...}</span></div><h1>PipelineProcessor.new ByteArrayResource() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="PipelineProcessor.java.html#L296" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PipelineProcessor.java.html#L300" class="el_method">getFilename()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$3.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$3.html
    new file mode 100644
    index 000000000..0742824d8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$3.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineProcessor.new ByteArrayResource() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">PipelineProcessor.new ByteArrayResource() {...}</span></div><h1>PipelineProcessor.new ByteArrayResource() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">15 of 15</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="PipelineProcessor.java.html#L320" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PipelineProcessor.java.html#L324" class="el_method">getFilename()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="5" alt="5"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$4.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$4.html
    new file mode 100644
    index 000000000..3fd6fd91d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor$4.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineProcessor.new ByteArrayResource() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">PipelineProcessor.new ByteArrayResource() {...}</span></div><h1>PipelineProcessor.new ByteArrayResource() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">13 of 13</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="PipelineProcessor.java.html#L356" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PipelineProcessor.java.html#L360" class="el_method">getFilename()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor.html
    new file mode 100644
    index 000000000..9655d3f02
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineProcessor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_class">PipelineProcessor</span></div><h1>PipelineProcessor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">808 of 808</td><td class="ctr2">0%</td><td class="bar">96 of 96</td><td class="ctr2">0%</td><td class="ctr1">62</td><td class="ctr2">62</td><td class="ctr1">182</td><td class="ctr2">182</td><td class="ctr1">14</td><td class="ctr2">14</td></tr></tfoot><tbody><tr><td id="a10"><a href="PipelineProcessor.java.html#L91" class="el_method">runPipelineAgainstFiles(List, PipelineConfig)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="362" alt="362"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="46" alt="46"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">24</td><td class="ctr2" id="g0">24</td><td class="ctr1" id="h0">86</td><td class="ctr2" id="i0">86</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a13"><a href="PipelineProcessor.java.html#L342" class="el_method">unzip(byte[])</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="84" alt="84"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f4">4</td><td class="ctr2" id="g4">4</td><td class="ctr1" id="h1">19</td><td class="ctr2" id="i1">19</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="PipelineProcessor.java.html#L285" class="el_method">generateInputFiles(File[])</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="69" alt="69"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h2">15</td><td class="ctr2" id="i2">15</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="PipelineProcessor.java.html#L267" class="el_method">extractFilename(ResponseEntity)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="56" alt="56"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">5</td><td class="ctr2" id="g3">5</td><td class="ctr1" id="h3">11</td><td class="ctr2" id="i3">11</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="PipelineProcessor.java.html#L313" class="el_method">generateInputFiles(MultipartFile[])</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="47" alt="47"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="6" alt="6"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f5">4</td><td class="ctr2" id="g5">4</td><td class="ctr1" id="h4">10</td><td class="ctr2" id="i4">10</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a8"><a href="PipelineProcessor.java.html#L238" class="el_method">processOutputFiles(String, ResponseEntity, List)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="44" alt="44"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f6">3</td><td class="ctr2" id="g6">3</td><td class="ctr1" id="h5">9</td><td class="ctr2" id="i5">9</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a9"><a href="PipelineProcessor.java.html#L61" class="el_method">removeTrailingNaming(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="34" alt="34"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f7">3</td><td class="ctr2" id="g7">3</td><td class="ctr1" id="h6">9</td><td class="ctr2" id="i6">9</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a11"><a href="PipelineProcessor.java.html#L221" class="el_method">sendWebRequest(String, MultiValueMap)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="33" alt="33"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h7">7</td><td class="ctr2" id="i7">7</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a5"><a href="PipelineProcessor.java.html#L334" class="el_method">isZip(byte[])</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="32" alt="32"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="12" alt="12"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f1">7</td><td class="ctr2" id="g1">7</td><td class="ctr1" id="h9">3</td><td class="ctr2" id="i9">3</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a7"><a href="PipelineProcessor.java.html#L53" class="el_method">PipelineProcessor(ApiDocService, UserServiceInterface, ServletContext)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="12" alt="12"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h8">5</td><td class="ctr2" id="i8">5</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a3"><a href="PipelineProcessor.java.html#L79" class="el_method">getApiKeyForUser()</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="11" alt="11"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h11">2</td><td class="ctr2" id="i11">2</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a4"><a href="PipelineProcessor.java.html#L84" class="el_method">getBaseUrl()</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="10" alt="10"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h10">3</td><td class="ctr2" id="i10">3</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a6"><a href="PipelineProcessor.java.html#L168" class="el_method">lambda$runPipelineAgainstFiles$0(List, Resource)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="10" alt="10"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h12">2</td><td class="ctr2" id="i12">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a12"><a href="PipelineProcessor.java.html#L41" class="el_method">static {...}</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">1</td><td class="ctr2" id="i13">1</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor.java.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor.java.html
    new file mode 100644
    index 000000000..faadf36f8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/PipelineProcessor.java.html
    @@ -0,0 +1,376 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineProcessor.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.pipeline</a> &gt; <span class="el_source">PipelineProcessor.java</span></div><h1>PipelineProcessor.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.pipeline;
    +
    +import java.io.*;
    +import java.net.URLDecoder;
    +import java.nio.charset.StandardCharsets;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Map.Entry;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipInputStream;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.core.io.ByteArrayResource;
    +import org.springframework.core.io.Resource;
    +import org.springframework.http.*;
    +import org.springframework.stereotype.Service;
    +import org.springframework.util.LinkedMultiValueMap;
    +import org.springframework.util.MultiValueMap;
    +import org.springframework.web.client.RestTemplate;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.github.pixee.security.ZipSecurity;
    +
    +import jakarta.servlet.ServletContext;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.SPDFApplication;
    +import stirling.software.SPDF.model.PipelineConfig;
    +import stirling.software.SPDF.model.PipelineOperation;
    +import stirling.software.SPDF.model.PipelineResult;
    +import stirling.software.SPDF.model.Role;
    +
    +@Service
    +<span class="nc" id="L41">@Slf4j</span>
    +public class PipelineProcessor {
    +
    +    private final ApiDocService apiDocService;
    +
    +    private final UserServiceInterface userService;
    +
    +    private final ServletContext servletContext;
    +
    +    public PipelineProcessor(
    +            ApiDocService apiDocService,
    +            @Autowired(required = false) UserServiceInterface userService,
    +<span class="nc" id="L53">            ServletContext servletContext) {</span>
    +<span class="nc" id="L54">        this.apiDocService = apiDocService;</span>
    +<span class="nc" id="L55">        this.userService = userService;</span>
    +<span class="nc" id="L56">        this.servletContext = servletContext;</span>
    +<span class="nc" id="L57">    }</span>
    +
    +    public static String removeTrailingNaming(String filename) {
    +        // Splitting filename into name and extension
    +<span class="nc" id="L61">        int dotIndex = filename.lastIndexOf(&quot;.&quot;);</span>
    +<span class="nc bnc" id="L62" title="All 2 branches missed.">        if (dotIndex == -1) {</span>
    +            // No extension found
    +<span class="nc" id="L64">            return filename;</span>
    +        }
    +<span class="nc" id="L66">        String name = filename.substring(0, dotIndex);</span>
    +<span class="nc" id="L67">        String extension = filename.substring(dotIndex);</span>
    +        // Finding the last underscore
    +<span class="nc" id="L69">        int underscoreIndex = name.lastIndexOf(&quot;_&quot;);</span>
    +<span class="nc bnc" id="L70" title="All 2 branches missed.">        if (underscoreIndex == -1) {</span>
    +            // No underscore found
    +<span class="nc" id="L72">            return filename;</span>
    +        }
    +        // Removing the last part and reattaching the extension
    +<span class="nc" id="L75">        return name.substring(0, underscoreIndex) + extension;</span>
    +    }
    +
    +    private String getApiKeyForUser() {
    +<span class="nc bnc" id="L79" title="All 2 branches missed.">        if (userService == null) return &quot;&quot;;</span>
    +<span class="nc" id="L80">        return userService.getApiKeyForUser(Role.INTERNAL_API_USER.getRoleId());</span>
    +    }
    +
    +    private String getBaseUrl() {
    +<span class="nc" id="L84">        String contextPath = servletContext.getContextPath();</span>
    +<span class="nc" id="L85">        String port = SPDFApplication.getStaticPort();</span>
    +<span class="nc" id="L86">        return &quot;http://localhost:&quot; + port + contextPath + &quot;/&quot;;</span>
    +    }
    +
    +    PipelineResult runPipelineAgainstFiles(List&lt;Resource&gt; outputFiles, PipelineConfig config)
    +            throws Exception {
    +<span class="nc" id="L91">        PipelineResult result = new PipelineResult();</span>
    +
    +<span class="nc" id="L93">        ByteArrayOutputStream logStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L94">        PrintStream logPrintStream = new PrintStream(logStream);</span>
    +<span class="nc" id="L95">        boolean hasErrors = false;</span>
    +<span class="nc bnc" id="L96" title="All 2 branches missed.">        for (PipelineOperation pipelineOperation : config.getOperations()) {</span>
    +<span class="nc" id="L97">            String operation = pipelineOperation.getOperation();</span>
    +<span class="nc" id="L98">            boolean isMultiInputOperation = apiDocService.isMultiInput(operation);</span>
    +<span class="nc" id="L99">            log.info(</span>
    +                    &quot;Running operation: {} isMultiInputOperation {}&quot;,
    +                    operation,
    +<span class="nc" id="L102">                    isMultiInputOperation);</span>
    +<span class="nc" id="L103">            Map&lt;String, Object&gt; parameters = pipelineOperation.getParameters();</span>
    +<span class="nc" id="L104">            List&lt;String&gt; inputFileTypes = apiDocService.getExtensionTypes(false, operation);</span>
    +<span class="nc bnc" id="L105" title="All 2 branches missed.">            if (inputFileTypes == null) {</span>
    +<span class="nc" id="L106">                inputFileTypes = new ArrayList&lt;String&gt;(Arrays.asList(&quot;ALL&quot;));</span>
    +            }
    +            // List outputFileTypes = apiDocService.getExtensionTypes(true, operation);
    +<span class="nc" id="L109">            String url = getBaseUrl() + operation;</span>
    +<span class="nc" id="L110">            List&lt;Resource&gt; newOutputFiles = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L111" title="All 2 branches missed.">            if (!isMultiInputOperation) {</span>
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">                for (Resource file : outputFiles) {</span>
    +<span class="nc" id="L113">                    boolean hasInputFileType = false;</span>
    +<span class="nc bnc" id="L114" title="All 2 branches missed.">                    for (String extension : inputFileTypes) {</span>
    +<span class="nc bnc" id="L115" title="All 4 branches missed.">                        if (&quot;ALL&quot;.equals(extension) || file.getFilename().endsWith(extension)) {</span>
    +<span class="nc" id="L116">                            hasInputFileType = true;</span>
    +<span class="nc" id="L117">                            MultiValueMap&lt;String, Object&gt; body = new LinkedMultiValueMap&lt;&gt;();</span>
    +<span class="nc" id="L118">                            body.add(&quot;fileInput&quot;, file);</span>
    +<span class="nc bnc" id="L119" title="All 2 branches missed.">                            for (Entry&lt;String, Object&gt; entry : parameters.entrySet()) {</span>
    +<span class="nc bnc" id="L120" title="All 2 branches missed.">                                if (entry.getValue() instanceof List&lt;?&gt; entryList) {</span>
    +<span class="nc bnc" id="L121" title="All 2 branches missed.">                                    for (Object item : entryList) {</span>
    +<span class="nc" id="L122">                                        body.add(entry.getKey(), item);</span>
    +<span class="nc" id="L123">                                    }</span>
    +                                } else {
    +<span class="nc" id="L125">                                    body.add(entry.getKey(), entry.getValue());</span>
    +                                }
    +<span class="nc" id="L127">                            }</span>
    +<span class="nc" id="L128">                            ResponseEntity&lt;byte[]&gt; response = sendWebRequest(url, body);</span>
    +                            // If the operation is filter and the response body is null or empty,
    +                            // skip
    +                            // this
    +                            // file
    +<span class="nc bnc" id="L133" title="All 2 branches missed.">                            if (operation.startsWith(&quot;filter-&quot;)</span>
    +<span class="nc bnc" id="L134" title="All 2 branches missed.">                                    &amp;&amp; (response.getBody() == null</span>
    +<span class="nc bnc" id="L135" title="All 2 branches missed.">                                            || response.getBody().length == 0)) {</span>
    +<span class="nc" id="L136">                                result.setFiltersApplied(true);</span>
    +<span class="nc" id="L137">                                log.info(&quot;Skipping file due to filtering {}&quot;, operation);</span>
    +<span class="nc" id="L138">                                continue;</span>
    +                            }
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">                            if (!HttpStatus.OK.equals(response.getStatusCode())) {</span>
    +<span class="nc" id="L141">                                logPrintStream.println(&quot;Error: &quot; + response.getBody());</span>
    +<span class="nc" id="L142">                                hasErrors = true;</span>
    +<span class="nc" id="L143">                                continue;</span>
    +                            }
    +<span class="nc" id="L145">                            processOutputFiles(operation, response, newOutputFiles);</span>
    +                        }
    +<span class="nc" id="L147">                    }</span>
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">                    if (!hasInputFileType) {</span>
    +<span class="nc" id="L149">                        logPrintStream.println(</span>
    +                                &quot;No files with extension &quot;
    +<span class="nc" id="L151">                                        + String.join(&quot;, &quot;, inputFileTypes)</span>
    +                                        + &quot; found for operation &quot;
    +                                        + operation);
    +<span class="nc" id="L154">                        hasErrors = true;</span>
    +                    }
    +<span class="nc" id="L156">                }</span>
    +            } else {
    +                // Filter and collect all files that match the inputFileExtension
    +                List&lt;Resource&gt; matchingFiles;
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">                if (inputFileTypes.contains(&quot;ALL&quot;)) {</span>
    +<span class="nc" id="L161">                    matchingFiles = new ArrayList&lt;&gt;(outputFiles);</span>
    +                } else {
    +<span class="nc" id="L163">                    final List&lt;String&gt; finalinputFileTypes = inputFileTypes;</span>
    +<span class="nc" id="L164">                    matchingFiles =</span>
    +<span class="nc" id="L165">                            outputFiles.stream()</span>
    +<span class="nc" id="L166">                                    .filter(</span>
    +                                            file -&gt;
    +<span class="nc" id="L168">                                                    finalinputFileTypes.stream()</span>
    +<span class="nc" id="L169">                                                            .anyMatch(file.getFilename()::endsWith))</span>
    +<span class="nc" id="L170">                                    .toList();</span>
    +                }
    +                // Check if there are matching files
    +<span class="nc bnc" id="L173" title="All 2 branches missed.">                if (!matchingFiles.isEmpty()) {</span>
    +                    // Create a new MultiValueMap for the request body
    +<span class="nc" id="L175">                    MultiValueMap&lt;String, Object&gt; body = new LinkedMultiValueMap&lt;&gt;();</span>
    +                    // Add all matching files to the body
    +<span class="nc bnc" id="L177" title="All 2 branches missed.">                    for (Resource file : matchingFiles) {</span>
    +<span class="nc" id="L178">                        body.add(&quot;fileInput&quot;, file);</span>
    +<span class="nc" id="L179">                    }</span>
    +<span class="nc bnc" id="L180" title="All 2 branches missed.">                    for (Entry&lt;String, Object&gt; entry : parameters.entrySet()) {</span>
    +<span class="nc bnc" id="L181" title="All 2 branches missed.">                        if (entry.getValue() instanceof List&lt;?&gt; entryList) {</span>
    +<span class="nc bnc" id="L182" title="All 2 branches missed.">                            for (Object item : entryList) {</span>
    +<span class="nc" id="L183">                                body.add(entry.getKey(), item);</span>
    +<span class="nc" id="L184">                            }</span>
    +                        } else {
    +<span class="nc" id="L186">                            body.add(entry.getKey(), entry.getValue());</span>
    +                        }
    +<span class="nc" id="L188">                    }</span>
    +<span class="nc" id="L189">                    ResponseEntity&lt;byte[]&gt; response = sendWebRequest(url, body);</span>
    +                    // Handle the response
    +<span class="nc bnc" id="L191" title="All 2 branches missed.">                    if (HttpStatus.OK.equals(response.getStatusCode())) {</span>
    +<span class="nc" id="L192">                        processOutputFiles(operation, response, newOutputFiles);</span>
    +                    } else {
    +                        // Log error if the response status is not OK
    +<span class="nc" id="L195">                        logPrintStream.println(</span>
    +<span class="nc" id="L196">                                &quot;Error in multi-input operation: &quot; + response.getBody());</span>
    +<span class="nc" id="L197">                        hasErrors = true;</span>
    +                    }
    +<span class="nc" id="L199">                } else {</span>
    +<span class="nc" id="L200">                    logPrintStream.println(</span>
    +                            &quot;No files with extension &quot;
    +<span class="nc" id="L202">                                    + String.join(&quot;, &quot;, inputFileTypes)</span>
    +                                    + &quot; found for multi-input operation &quot;
    +                                    + operation);
    +<span class="nc" id="L205">                    hasErrors = true;</span>
    +                }
    +            }
    +<span class="nc" id="L208">            logPrintStream.close();</span>
    +<span class="nc" id="L209">            outputFiles = newOutputFiles;</span>
    +<span class="nc" id="L210">        }</span>
    +<span class="nc bnc" id="L211" title="All 2 branches missed.">        if (hasErrors) {</span>
    +<span class="nc" id="L212">            log.error(&quot;Errors occurred during processing. Log: {}&quot;, logStream.toString());</span>
    +        }
    +<span class="nc" id="L214">        result.setHasErrors(hasErrors);</span>
    +<span class="nc" id="L215">        result.setFiltersApplied(hasErrors);</span>
    +<span class="nc" id="L216">        result.setOutputFiles(outputFiles);</span>
    +<span class="nc" id="L217">        return result;</span>
    +    }
    +
    +    private ResponseEntity&lt;byte[]&gt; sendWebRequest(String url, MultiValueMap&lt;String, Object&gt; body) {
    +<span class="nc" id="L221">        RestTemplate restTemplate = new RestTemplate();</span>
    +        // Set up headers, including API key
    +<span class="nc" id="L223">        HttpHeaders headers = new HttpHeaders();</span>
    +<span class="nc" id="L224">        String apiKey = getApiKeyForUser();</span>
    +<span class="nc" id="L225">        headers.add(&quot;X-API-KEY&quot;, apiKey);</span>
    +<span class="nc" id="L226">        headers.setContentType(MediaType.MULTIPART_FORM_DATA);</span>
    +        // Create HttpEntity with the body and headers
    +<span class="nc" id="L228">        HttpEntity&lt;MultiValueMap&lt;String, Object&gt;&gt; entity = new HttpEntity&lt;&gt;(body, headers);</span>
    +        // Make the request to the REST endpoint
    +<span class="nc" id="L230">        return restTemplate.exchange(url, HttpMethod.POST, entity, byte[].class);</span>
    +    }
    +
    +    private List&lt;Resource&gt; processOutputFiles(
    +            String operation, ResponseEntity&lt;byte[]&gt; response, List&lt;Resource&gt; newOutputFiles)
    +            throws IOException {
    +        // Define filename
    +        String newFilename;
    +<span class="nc bnc" id="L238" title="All 2 branches missed.">        if (operation.contains(&quot;auto-rename&quot;)) {</span>
    +            // If the operation is &quot;auto-rename&quot;, generate a new filename.
    +            // This is a simple example of generating a filename using current timestamp.
    +            // Modify as per your needs.
    +<span class="nc" id="L242">            newFilename = extractFilename(response);</span>
    +        } else {
    +            // Otherwise, keep the original filename.
    +<span class="nc" id="L245">            newFilename = removeTrailingNaming(extractFilename(response));</span>
    +        }
    +        // Check if the response body is a zip file
    +<span class="nc bnc" id="L248" title="All 2 branches missed.">        if (isZip(response.getBody())) {</span>
    +            // Unzip the file and add all the files to the new output files
    +<span class="nc" id="L250">            newOutputFiles.addAll(unzip(response.getBody()));</span>
    +        } else {
    +<span class="nc" id="L252">            Resource outputResource =</span>
    +<span class="nc" id="L253">                    new ByteArrayResource(response.getBody()) {</span>
    +
    +                        @Override
    +                        public String getFilename() {
    +<span class="nc" id="L257">                            return newFilename;</span>
    +                        }
    +                    };
    +<span class="nc" id="L260">            newOutputFiles.add(outputResource);</span>
    +        }
    +<span class="nc" id="L262">        return newOutputFiles;</span>
    +    }
    +
    +    public String extractFilename(ResponseEntity&lt;byte[]&gt; response) {
    +        // Default filename if not found
    +<span class="nc" id="L267">        String filename = &quot;default-filename.ext&quot;;</span>
    +<span class="nc" id="L268">        HttpHeaders headers = response.getHeaders();</span>
    +<span class="nc" id="L269">        String contentDisposition = headers.getFirst(HttpHeaders.CONTENT_DISPOSITION);</span>
    +<span class="nc bnc" id="L270" title="All 4 branches missed.">        if (contentDisposition != null &amp;&amp; !contentDisposition.isEmpty()) {</span>
    +<span class="nc" id="L271">            String[] parts = contentDisposition.split(&quot;;&quot;);</span>
    +<span class="nc bnc" id="L272" title="All 2 branches missed.">            for (String part : parts) {</span>
    +<span class="nc bnc" id="L273" title="All 2 branches missed.">                if (part.trim().startsWith(&quot;filename&quot;)) {</span>
    +                    // Extracts filename and removes quotes if present
    +<span class="nc" id="L275">                    filename = part.split(&quot;=&quot;)[1].trim().replace(&quot;\&quot;&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L276">                    filename = URLDecoder.decode(filename, StandardCharsets.UTF_8);</span>
    +<span class="nc" id="L277">                    break;</span>
    +                }
    +            }
    +        }
    +<span class="nc" id="L281">        return filename;</span>
    +    }
    +
    +    List&lt;Resource&gt; generateInputFiles(File[] files) throws Exception {
    +<span class="nc bnc" id="L285" title="All 4 branches missed.">        if (files == null || files.length == 0) {</span>
    +<span class="nc" id="L286">            log.info(&quot;No files&quot;);</span>
    +<span class="nc" id="L287">            return null;</span>
    +        }
    +<span class="nc" id="L289">        List&lt;Resource&gt; outputFiles = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L290" title="All 2 branches missed.">        for (File file : files) {</span>
    +<span class="nc" id="L291">            Path path = Paths.get(file.getAbsolutePath());</span>
    +            // debug statement
    +<span class="nc" id="L293">            log.info(&quot;Reading file: &quot; + path);</span>
    +<span class="nc bnc" id="L294" title="All 2 branches missed.">            if (Files.exists(path)) {</span>
    +<span class="nc" id="L295">                Resource fileResource =</span>
    +<span class="nc" id="L296">                        new ByteArrayResource(Files.readAllBytes(path)) {</span>
    +
    +                            @Override
    +                            public String getFilename() {
    +<span class="nc" id="L300">                                return file.getName();</span>
    +                            }
    +                        };
    +<span class="nc" id="L303">                outputFiles.add(fileResource);</span>
    +<span class="nc" id="L304">            } else {</span>
    +<span class="nc" id="L305">                log.info(&quot;File not found: &quot; + path);</span>
    +            }
    +        }
    +<span class="nc" id="L308">        log.info(&quot;Files successfully loaded. Starting processing...&quot;);</span>
    +<span class="nc" id="L309">        return outputFiles;</span>
    +    }
    +
    +    List&lt;Resource&gt; generateInputFiles(MultipartFile[] files) throws Exception {
    +<span class="nc bnc" id="L313" title="All 4 branches missed.">        if (files == null || files.length == 0) {</span>
    +<span class="nc" id="L314">            log.info(&quot;No files&quot;);</span>
    +<span class="nc" id="L315">            return null;</span>
    +        }
    +<span class="nc" id="L317">        List&lt;Resource&gt; outputFiles = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L318" title="All 2 branches missed.">        for (MultipartFile file : files) {</span>
    +<span class="nc" id="L319">            Resource fileResource =</span>
    +<span class="nc" id="L320">                    new ByteArrayResource(file.getBytes()) {</span>
    +
    +                        @Override
    +                        public String getFilename() {
    +<span class="nc" id="L324">                            return Filenames.toSimpleFileName(file.getOriginalFilename());</span>
    +                        }
    +                    };
    +<span class="nc" id="L327">            outputFiles.add(fileResource);</span>
    +        }
    +<span class="nc" id="L329">        log.info(&quot;Files successfully loaded. Starting processing...&quot;);</span>
    +<span class="nc" id="L330">        return outputFiles;</span>
    +    }
    +
    +    private boolean isZip(byte[] data) {
    +<span class="nc bnc" id="L334" title="All 4 branches missed.">        if (data == null || data.length &lt; 4) {</span>
    +<span class="nc" id="L335">            return false;</span>
    +        }
    +        // Check the first four bytes of the data against the standard zip magic number
    +<span class="nc bnc" id="L338" title="All 8 branches missed.">        return data[0] == 0x50 &amp;&amp; data[1] == 0x4B &amp;&amp; data[2] == 0x03 &amp;&amp; data[3] == 0x04;</span>
    +    }
    +
    +    private List&lt;Resource&gt; unzip(byte[] data) throws IOException {
    +<span class="nc" id="L342">        log.info(&quot;Unzipping data of length: {}&quot;, data.length);</span>
    +<span class="nc" id="L343">        List&lt;Resource&gt; unzippedFiles = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L344">        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);</span>
    +<span class="nc" id="L345">                ZipInputStream zis = ZipSecurity.createHardenedInputStream(bais)) {</span>
    +            ZipEntry entry;
    +<span class="nc bnc" id="L347" title="All 2 branches missed.">            while ((entry = zis.getNextEntry()) != null) {</span>
    +<span class="nc" id="L348">                ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L349">                byte[] buffer = new byte[1024];</span>
    +                int count;
    +<span class="nc bnc" id="L351" title="All 2 branches missed.">                while ((count = zis.read(buffer)) != -1) {</span>
    +<span class="nc" id="L352">                    baos.write(buffer, 0, count);</span>
    +                }
    +<span class="nc" id="L354">                final String filename = entry.getName();</span>
    +<span class="nc" id="L355">                Resource fileResource =</span>
    +<span class="nc" id="L356">                        new ByteArrayResource(baos.toByteArray()) {</span>
    +
    +                            @Override
    +                            public String getFilename() {
    +<span class="nc" id="L360">                                return filename;</span>
    +                            }
    +                        };
    +                // If the unzipped file is a zip file, unzip it
    +<span class="nc bnc" id="L364" title="All 2 branches missed.">                if (isZip(baos.toByteArray())) {</span>
    +<span class="nc" id="L365">                    log.info(&quot;File {} is a zip file. Unzipping...&quot;, filename);</span>
    +<span class="nc" id="L366">                    unzippedFiles.addAll(unzip(baos.toByteArray()));</span>
    +                } else {
    +<span class="nc" id="L368">                    unzippedFiles.add(fileResource);</span>
    +                }
    +<span class="nc" id="L370">            }</span>
    +        }
    +<span class="nc" id="L372">        log.info(&quot;Unzipping completed. {} files were unzipped.&quot;, unzippedFiles.size());</span>
    +<span class="nc" id="L373">        return unzippedFiles;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/index.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/index.html
    new file mode 100644
    index 000000000..4ea6592bf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.pipeline</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.pipeline</span></div><h1>stirling.software.SPDF.controller.api.pipeline</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2,508 of 2,508</td><td class="ctr2">0%</td><td class="bar">208 of 208</td><td class="ctr2">0%</td><td class="ctr1">165</td><td class="ctr2">165</td><td class="ctr1">550</td><td class="ctr2">550</td><td class="ctr1">61</td><td class="ctr2">61</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a4"><a href="PipelineProcessor.html" class="el_class">PipelineProcessor</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="808" alt="808"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="96" alt="96"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">62</td><td class="ctr2" id="g0">62</td><td class="ctr1" id="h1">182</td><td class="ctr2" id="i1">182</td><td class="ctr1" id="j1">14</td><td class="ctr2" id="k1">14</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a2"><a href="PipelineDirectoryProcessor.html" class="el_class">PipelineDirectoryProcessor</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="118" height="10" title="797" alt="797"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="60" alt="60"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">54</td><td class="ctr2" id="g1">54</td><td class="ctr1" id="h0">203</td><td class="ctr2" id="i0">203</td><td class="ctr1" id="j0">24</td><td class="ctr2" id="k0">24</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a0"><a href="ApiDocService.html" class="el_class">ApiDocService</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="84" height="10" title="568" alt="568"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="32" alt="32"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">25</td><td class="ctr2" id="g2">25</td><td class="ctr1" id="h2">89</td><td class="ctr2" id="i2">89</td><td class="ctr1" id="j2">9</td><td class="ctr2" id="k2">9</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a1"><a href="PipelineController.html" class="el_class">PipelineController</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="242" alt="242"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="16" alt="16"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">11</td><td class="ctr2" id="g3">11</td><td class="ctr1" id="h3">63</td><td class="ctr2" id="i3">63</td><td class="ctr1" id="j3">3</td><td class="ctr2" id="k3">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a3"><a href="PipelineDirectoryProcessor$1.html" class="el_class">PipelineDirectoryProcessor.new SimpleFileVisitor() {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="38" alt="38"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">5</td><td class="ctr2" id="g4">5</td><td class="ctr1" id="h4">9</td><td class="ctr2" id="i4">9</td><td class="ctr1" id="j4">3</td><td class="ctr2" id="k4">3</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a5"><a href="PipelineProcessor$3.html" class="el_class">PipelineProcessor.new ByteArrayResource() {...}</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="15" alt="15"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h5">2</td><td class="ctr2" id="i5">2</td><td class="ctr1" id="j5">2</td><td class="ctr2" id="k5">2</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a6"><a href="PipelineProcessor$2.html" class="el_class">PipelineProcessor.new ByteArrayResource() {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="14" alt="14"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h6">2</td><td class="ctr2" id="i6">2</td><td class="ctr1" id="j6">2</td><td class="ctr2" id="k6">2</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a7"><a href="PipelineProcessor$4.html" class="el_class">PipelineProcessor.new ByteArrayResource() {...}</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="13" alt="13"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h7">2</td><td class="ctr2" id="i7">2</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a8"><a href="PipelineProcessor$1.html" class="el_class">PipelineProcessor.new ByteArrayResource() {...}</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="13" alt="13"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j8">2</td><td class="ctr2" id="k8">2</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.pipeline/index.source.html b/testResult/html/stirling.software.SPDF.controller.api.pipeline/index.source.html
    new file mode 100644
    index 000000000..7de3cb467
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.pipeline/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.pipeline</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.pipeline</span></div><h1>stirling.software.SPDF.controller.api.pipeline</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2,508 of 2,508</td><td class="ctr2">0%</td><td class="bar">208 of 208</td><td class="ctr2">0%</td><td class="ctr1">165</td><td class="ctr2">165</td><td class="ctr1">550</td><td class="ctr2">550</td><td class="ctr1">61</td><td class="ctr2">61</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a3"><a href="PipelineProcessor.java.html" class="el_source">PipelineProcessor.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="863" alt="863"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="96" alt="96"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">70</td><td class="ctr2" id="g0">70</td><td class="ctr1" id="h1">186</td><td class="ctr2" id="i1">186</td><td class="ctr1" id="j1">22</td><td class="ctr2" id="k1">22</td><td class="ctr1" id="l0">5</td><td class="ctr2" id="m0">5</td></tr><tr><td id="a2"><a href="PipelineDirectoryProcessor.java.html" class="el_source">PipelineDirectoryProcessor.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="116" height="10" title="835" alt="835"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="64" alt="64"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">59</td><td class="ctr2" id="g1">59</td><td class="ctr1" id="h0">212</td><td class="ctr2" id="i0">212</td><td class="ctr1" id="j0">27</td><td class="ctr2" id="k0">27</td><td class="ctr1" id="l1">2</td><td class="ctr2" id="m1">2</td></tr><tr><td id="a0"><a href="ApiDocService.java.html" class="el_source">ApiDocService.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="78" height="10" title="568" alt="568"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="32" alt="32"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">25</td><td class="ctr2" id="g2">25</td><td class="ctr1" id="h2">89</td><td class="ctr2" id="i2">89</td><td class="ctr1" id="j2">9</td><td class="ctr2" id="k2">9</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a1"><a href="PipelineController.java.html" class="el_source">PipelineController.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="242" alt="242"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="16" alt="16"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">11</td><td class="ctr2" id="g3">11</td><td class="ctr1" id="h3">63</td><td class="ctr2" id="i3">63</td><td class="ctr1" id="j3">3</td><td class="ctr2" id="k3">3</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController$CreateSignature.html b/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController$CreateSignature.html
    new file mode 100644
    index 000000000..d71a7869f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController$CreateSignature.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CertSignController.CreateSignature</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">CertSignController.CreateSignature</span></div><h1>CertSignController.CreateSignature</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">287 of 287</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">73</td><td class="ctr2">73</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="CertSignController.java.html#L250" class="el_method">createVisibleSignature(PDDocument, PDSignature, Integer, Boolean)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="252" alt="252"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">62</td><td class="ctr2" id="i0">62</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CertSignController.java.html#L234" class="el_method">CertSignController.CreateSignature(CertSignController, KeyStore, char[])</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="35" alt="35"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">11</td><td class="ctr2" id="i1">11</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController.html b/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController.html
    new file mode 100644
    index 000000000..478d52741
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CertSignController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">CertSignController</span></div><h1>CertSignController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">311 of 311</td><td class="ctr2">0%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">79</td><td class="ctr2">79</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a4"><a href="CertSignController.java.html#L137" class="el_method">signPDFWithCert(SignPDFWithCertRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="152" alt="152"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">36</td><td class="ctr2" id="i0">36</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="CertSignController.java.html#L199" class="el_method">getPrivateKeyFromPEM(byte[], String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="52" height="10" title="67" alt="67"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h2">14</td><td class="ctr2" id="i2">14</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="CertSignController.java.html#L102" class="el_method">sign(CustomPDFDocumentFactory, MultipartFile, OutputStream, CertSignController.CreateSignature, Boolean, Integer, String, String, String, Boolean)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="65" alt="65"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h1">21</td><td class="ctr2" id="i1">21</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="CertSignController.java.html#L221" class="el_method">getCertificateFromPEM(byte[])</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="12" alt="12"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h5">2</td><td class="ctr2" id="i5">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="CertSignController.java.html#L76" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="9" alt="9"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="CertSignController.java.html#L87" class="el_method">CertSignController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h4">3</td><td class="ctr2" id="i4">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController.java.html b/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController.java.html
    new file mode 100644
    index 000000000..122dc34c9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/CertSignController.java.html
    @@ -0,0 +1,334 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CertSignController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_source">CertSignController.java</span></div><h1>CertSignController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.security;
    +
    +import java.awt.*;
    +import java.io.*;
    +import java.nio.file.Files;
    +import java.security.*;
    +import java.security.cert.Certificate;
    +import java.security.cert.CertificateException;
    +import java.security.cert.CertificateFactory;
    +import java.security.cert.X509Certificate;
    +import java.util.Calendar;
    +import java.util.List;
    +
    +import org.apache.commons.io.FileUtils;
    +import org.apache.pdfbox.examples.signature.CreateSignatureBase;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.PDResources;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.common.PDStream;
    +import org.apache.pdfbox.pdmodel.font.PDFont;
    +import org.apache.pdfbox.pdmodel.font.PDType1Font;
    +import org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName;
    +import org.apache.pdfbox.pdmodel.graphics.blend.BlendMode;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
    +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
    +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureOptions;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDField;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
    +import org.apache.pdfbox.util.Matrix;
    +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
    +import org.bouncycastle.asn1.x500.RDN;
    +import org.bouncycastle.asn1.x500.X500Name;
    +import org.bouncycastle.asn1.x500.style.BCStyle;
    +import org.bouncycastle.asn1.x500.style.IETFUtils;
    +import org.bouncycastle.jce.provider.BouncyCastleProvider;
    +import org.bouncycastle.openssl.PEMDecryptorProvider;
    +import org.bouncycastle.openssl.PEMEncryptedKeyPair;
    +import org.bouncycastle.openssl.PEMKeyPair;
    +import org.bouncycastle.openssl.PEMParser;
    +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
    +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
    +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
    +import org.bouncycastle.operator.InputDecryptorProvider;
    +import org.bouncycastle.operator.OperatorCreationException;
    +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
    +import org.bouncycastle.pkcs.PKCSException;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/security&quot;)
    +<span class="nc" id="L76">@Slf4j</span>
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +public class CertSignController {
    +
    +    static {
    +<span class="nc" id="L81">        Security.addProvider(new BouncyCastleProvider());</span>
    +<span class="nc" id="L82">    }</span>
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L87">    public CertSignController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L88">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L89">    }</span>
    +
    +    private static void sign(
    +            CustomPDFDocumentFactory pdfDocumentFactory,
    +            MultipartFile input,
    +            OutputStream output,
    +            CreateSignature instance,
    +            Boolean showSignature,
    +            Integer pageNumber,
    +            String name,
    +            String location,
    +            String reason,
    +            Boolean showLogo) {
    +<span class="nc" id="L102">        try (PDDocument doc = pdfDocumentFactory.load(input)) {</span>
    +<span class="nc" id="L103">            PDSignature signature = new PDSignature();</span>
    +<span class="nc" id="L104">            signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);</span>
    +<span class="nc" id="L105">            signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);</span>
    +<span class="nc" id="L106">            signature.setName(name);</span>
    +<span class="nc" id="L107">            signature.setLocation(location);</span>
    +<span class="nc" id="L108">            signature.setReason(reason);</span>
    +<span class="nc" id="L109">            signature.setSignDate(Calendar.getInstance());</span>
    +
    +<span class="nc bnc" id="L111" title="All 2 branches missed.">            if (showSignature) {</span>
    +<span class="nc" id="L112">                SignatureOptions signatureOptions = new SignatureOptions();</span>
    +<span class="nc" id="L113">                signatureOptions.setVisualSignature(</span>
    +<span class="nc" id="L114">                        instance.createVisibleSignature(doc, signature, pageNumber, showLogo));</span>
    +<span class="nc" id="L115">                signatureOptions.setPage(pageNumber);</span>
    +
    +<span class="nc" id="L117">                doc.addSignature(signature, instance, signatureOptions);</span>
    +
    +<span class="nc" id="L119">            } else {</span>
    +<span class="nc" id="L120">                doc.addSignature(signature, instance);</span>
    +            }
    +<span class="nc" id="L122">            doc.saveIncremental(output);</span>
    +<span class="nc" id="L123">        } catch (Exception e) {</span>
    +<span class="nc" id="L124">            log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L125">        }</span>
    +<span class="nc" id="L126">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/cert-sign&quot;)
    +    @Operation(
    +            summary = &quot;Sign PDF with a Digital Certificate&quot;,
    +            description =
    +                    &quot;This endpoint accepts a PDF file, a digital certificate and related&quot;
    +                            + &quot; information to sign the PDF. It then returns the digitally signed PDF&quot;
    +                            + &quot; file. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; signPDFWithCert(@ModelAttribute SignPDFWithCertRequest request)
    +            throws Exception {
    +<span class="nc" id="L137">        MultipartFile pdf = request.getFileInput();</span>
    +<span class="nc" id="L138">        String certType = request.getCertType();</span>
    +<span class="nc" id="L139">        MultipartFile privateKeyFile = request.getPrivateKeyFile();</span>
    +<span class="nc" id="L140">        MultipartFile certFile = request.getCertFile();</span>
    +<span class="nc" id="L141">        MultipartFile p12File = request.getP12File();</span>
    +<span class="nc" id="L142">        MultipartFile jksfile = request.getJksFile();</span>
    +<span class="nc" id="L143">        String password = request.getPassword();</span>
    +<span class="nc" id="L144">        Boolean showSignature = request.isShowSignature();</span>
    +<span class="nc" id="L145">        String reason = request.getReason();</span>
    +<span class="nc" id="L146">        String location = request.getLocation();</span>
    +<span class="nc" id="L147">        String name = request.getName();</span>
    +<span class="nc" id="L148">        Integer pageNumber = request.getPageNumber() - 1;</span>
    +<span class="nc" id="L149">        Boolean showLogo = request.isShowLogo();</span>
    +
    +<span class="nc bnc" id="L151" title="All 2 branches missed.">        if (certType == null) {</span>
    +<span class="nc" id="L152">            throw new IllegalArgumentException(&quot;Cert type must be provided&quot;);</span>
    +        }
    +
    +<span class="nc" id="L155">        KeyStore ks = null;</span>
    +
    +<span class="nc bnc" id="L157" title="All 4 branches missed.">        switch (certType) {</span>
    +            case &quot;PEM&quot;:
    +<span class="nc" id="L159">                ks = KeyStore.getInstance(&quot;JKS&quot;);</span>
    +<span class="nc" id="L160">                ks.load(null);</span>
    +<span class="nc" id="L161">                PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password);</span>
    +<span class="nc" id="L162">                Certificate cert = (Certificate) getCertificateFromPEM(certFile.getBytes());</span>
    +<span class="nc" id="L163">                ks.setKeyEntry(</span>
    +<span class="nc" id="L164">                        &quot;alias&quot;, privateKey, password.toCharArray(), new Certificate[] {cert});</span>
    +<span class="nc" id="L165">                break;</span>
    +            case &quot;PKCS12&quot;:
    +<span class="nc" id="L167">                ks = KeyStore.getInstance(&quot;PKCS12&quot;);</span>
    +<span class="nc" id="L168">                ks.load(p12File.getInputStream(), password.toCharArray());</span>
    +<span class="nc" id="L169">                break;</span>
    +            case &quot;JKS&quot;:
    +<span class="nc" id="L171">                ks = KeyStore.getInstance(&quot;JKS&quot;);</span>
    +<span class="nc" id="L172">                ks.load(jksfile.getInputStream(), password.toCharArray());</span>
    +<span class="nc" id="L173">                break;</span>
    +            default:
    +<span class="nc" id="L175">                throw new IllegalArgumentException(&quot;Invalid cert type: &quot; + certType);</span>
    +        }
    +
    +<span class="nc" id="L178">        CreateSignature createSignature = new CreateSignature(ks, password.toCharArray());</span>
    +<span class="nc" id="L179">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L180">        sign(</span>
    +                pdfDocumentFactory,
    +                pdf,
    +                baos,
    +                createSignature,
    +                showSignature,
    +                pageNumber,
    +                name,
    +                location,
    +                reason,
    +                showLogo);
    +<span class="nc" id="L191">        return WebResponseUtils.boasToWebResponse(</span>
    +                baos,
    +<span class="nc" id="L193">                Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_signed.pdf&quot;);
    +    }
    +
    +    private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password)
    +            throws IOException, OperatorCreationException, PKCSException {
    +<span class="nc" id="L199">        try (PEMParser pemParser =</span>
    +                new PEMParser(new InputStreamReader(new ByteArrayInputStream(pemBytes)))) {
    +<span class="nc" id="L201">            Object pemObject = pemParser.readObject();</span>
    +<span class="nc" id="L202">            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(&quot;BC&quot;);</span>
    +            PrivateKeyInfo pkInfo;
    +<span class="nc bnc" id="L204" title="All 2 branches missed.">            if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo pkcs8EncryptedPrivateKeyInfo) {</span>
    +<span class="nc" id="L205">                InputDecryptorProvider decProv =</span>
    +<span class="nc" id="L206">                        new JceOpenSSLPKCS8DecryptorProviderBuilder().build(password.toCharArray());</span>
    +<span class="nc" id="L207">                pkInfo = pkcs8EncryptedPrivateKeyInfo.decryptPrivateKeyInfo(decProv);</span>
    +<span class="nc bnc" id="L208" title="All 2 branches missed.">            } else if (pemObject instanceof PEMEncryptedKeyPair pemEncryptedKeyPair) {</span>
    +<span class="nc" id="L209">                PEMDecryptorProvider decProv =</span>
    +<span class="nc" id="L210">                        new JcePEMDecryptorProviderBuilder().build(password.toCharArray());</span>
    +<span class="nc" id="L211">                pkInfo = pemEncryptedKeyPair.decryptKeyPair(decProv).getPrivateKeyInfo();</span>
    +<span class="nc" id="L212">            } else {</span>
    +<span class="nc" id="L213">                pkInfo = ((PEMKeyPair) pemObject).getPrivateKeyInfo();</span>
    +            }
    +<span class="nc" id="L215">            return converter.getPrivateKey(pkInfo);</span>
    +        }
    +    }
    +
    +    private Certificate getCertificateFromPEM(byte[] pemBytes)
    +            throws IOException, CertificateException {
    +<span class="nc" id="L221">        try (ByteArrayInputStream bis = new ByteArrayInputStream(pemBytes)) {</span>
    +<span class="nc" id="L222">            return CertificateFactory.getInstance(&quot;X.509&quot;).generateCertificate(bis);</span>
    +        }
    +    }
    +
    +    class CreateSignature extends CreateSignatureBase {
    +        File logoFile;
    +
    +        public CreateSignature(KeyStore keystore, char[] pin)
    +                throws KeyStoreException,
    +                        UnrecoverableKeyException,
    +                        NoSuchAlgorithmException,
    +                        IOException,
    +<span class="nc" id="L234">                        CertificateException {</span>
    +<span class="nc" id="L235">            super(keystore, pin);</span>
    +<span class="nc" id="L236">            ClassPathResource resource = new ClassPathResource(&quot;static/images/signature.png&quot;);</span>
    +<span class="nc" id="L237">            try (InputStream is = resource.getInputStream()) {</span>
    +<span class="nc" id="L238">                logoFile = Files.createTempFile(&quot;signature&quot;, &quot;.png&quot;).toFile();</span>
    +<span class="nc" id="L239">                FileUtils.copyInputStreamToFile(is, logoFile);</span>
    +<span class="nc" id="L240">            } catch (IOException e) {</span>
    +<span class="nc" id="L241">                log.error(&quot;Failed to load image signature file&quot;);</span>
    +<span class="nc" id="L242">                throw e;</span>
    +<span class="nc" id="L243">            }</span>
    +<span class="nc" id="L244">        }</span>
    +
    +        public InputStream createVisibleSignature(
    +                PDDocument srcDoc, PDSignature signature, Integer pageNumber, Boolean showLogo)
    +                throws IOException {
    +            // modified from org.apache.pdfbox.examples.signature.CreateVisibleSignature2
    +<span class="nc" id="L250">            try (PDDocument doc = new PDDocument()) {</span>
    +<span class="nc" id="L251">                PDPage page = new PDPage(srcDoc.getPage(pageNumber).getMediaBox());</span>
    +<span class="nc" id="L252">                doc.addPage(page);</span>
    +<span class="nc" id="L253">                PDAcroForm acroForm = new PDAcroForm(doc);</span>
    +<span class="nc" id="L254">                doc.getDocumentCatalog().setAcroForm(acroForm);</span>
    +<span class="nc" id="L255">                PDSignatureField signatureField = new PDSignatureField(acroForm);</span>
    +<span class="nc" id="L256">                PDAnnotationWidget widget = signatureField.getWidgets().get(0);</span>
    +<span class="nc" id="L257">                List&lt;PDField&gt; acroFormFields = acroForm.getFields();</span>
    +<span class="nc" id="L258">                acroForm.setSignaturesExist(true);</span>
    +<span class="nc" id="L259">                acroForm.setAppendOnly(true);</span>
    +<span class="nc" id="L260">                acroForm.getCOSObject().setDirect(true);</span>
    +<span class="nc" id="L261">                acroFormFields.add(signatureField);</span>
    +
    +<span class="nc" id="L263">                PDRectangle rect = new PDRectangle(0, 0, 200, 50);</span>
    +
    +<span class="nc" id="L265">                widget.setRectangle(rect);</span>
    +
    +                // from PDVisualSigBuilder.createHolderForm()
    +<span class="nc" id="L268">                PDStream stream = new PDStream(doc);</span>
    +<span class="nc" id="L269">                PDFormXObject form = new PDFormXObject(stream);</span>
    +<span class="nc" id="L270">                PDResources res = new PDResources();</span>
    +<span class="nc" id="L271">                form.setResources(res);</span>
    +<span class="nc" id="L272">                form.setFormType(1);</span>
    +<span class="nc" id="L273">                PDRectangle bbox = new PDRectangle(rect.getWidth(), rect.getHeight());</span>
    +<span class="nc" id="L274">                float height = bbox.getHeight();</span>
    +<span class="nc" id="L275">                form.setBBox(bbox);</span>
    +<span class="nc" id="L276">                PDFont font = new PDType1Font(FontName.TIMES_BOLD);</span>
    +
    +                // from PDVisualSigBuilder.createAppearanceDictionary()
    +<span class="nc" id="L279">                PDAppearanceDictionary appearance = new PDAppearanceDictionary();</span>
    +<span class="nc" id="L280">                appearance.getCOSObject().setDirect(true);</span>
    +<span class="nc" id="L281">                PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());</span>
    +<span class="nc" id="L282">                appearance.setNormalAppearance(appearanceStream);</span>
    +<span class="nc" id="L283">                widget.setAppearance(appearance);</span>
    +
    +<span class="nc" id="L285">                try (PDPageContentStream cs = new PDPageContentStream(doc, appearanceStream)) {</span>
    +<span class="nc bnc" id="L286" title="All 2 branches missed.">                    if (showLogo) {</span>
    +<span class="nc" id="L287">                        cs.saveGraphicsState();</span>
    +<span class="nc" id="L288">                        PDExtendedGraphicsState extState = new PDExtendedGraphicsState();</span>
    +<span class="nc" id="L289">                        extState.setBlendMode(BlendMode.MULTIPLY);</span>
    +<span class="nc" id="L290">                        extState.setNonStrokingAlphaConstant(0.5f);</span>
    +<span class="nc" id="L291">                        cs.setGraphicsStateParameters(extState);</span>
    +<span class="nc" id="L292">                        cs.transform(Matrix.getScaleInstance(0.08f, 0.08f));</span>
    +<span class="nc" id="L293">                        PDImageXObject img =</span>
    +<span class="nc" id="L294">                                PDImageXObject.createFromFileByExtension(logoFile, doc);</span>
    +<span class="nc" id="L295">                        cs.drawImage(img, 100, 0);</span>
    +<span class="nc" id="L296">                        cs.restoreGraphicsState();</span>
    +                    }
    +
    +                    // show text
    +<span class="nc" id="L300">                    float fontSize = 10;</span>
    +<span class="nc" id="L301">                    float leading = fontSize * 1.5f;</span>
    +<span class="nc" id="L302">                    cs.beginText();</span>
    +<span class="nc" id="L303">                    cs.setFont(font, fontSize);</span>
    +<span class="nc" id="L304">                    cs.setNonStrokingColor(Color.black);</span>
    +<span class="nc" id="L305">                    cs.newLineAtOffset(fontSize, height - leading);</span>
    +<span class="nc" id="L306">                    cs.setLeading(leading);</span>
    +
    +<span class="nc" id="L308">                    X509Certificate cert = (X509Certificate) getCertificateChain()[0];</span>
    +
    +                    // https://stackoverflow.com/questions/2914521/
    +<span class="nc" id="L311">                    X500Name x500Name = new X500Name(cert.getSubjectX500Principal().getName());</span>
    +<span class="nc" id="L312">                    RDN cn = x500Name.getRDNs(BCStyle.CN)[0];</span>
    +<span class="nc" id="L313">                    String name = IETFUtils.valueToString(cn.getFirst().getValue());</span>
    +
    +<span class="nc" id="L315">                    String date = signature.getSignDate().getTime().toString();</span>
    +<span class="nc" id="L316">                    String reason = signature.getReason();</span>
    +
    +<span class="nc" id="L318">                    cs.showText(&quot;Signed by &quot; + name);</span>
    +<span class="nc" id="L319">                    cs.newLine();</span>
    +<span class="nc" id="L320">                    cs.showText(date);</span>
    +<span class="nc" id="L321">                    cs.newLine();</span>
    +<span class="nc" id="L322">                    cs.showText(reason);</span>
    +
    +<span class="nc" id="L324">                    cs.endText();</span>
    +                }
    +
    +<span class="nc" id="L327">                ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L328">                doc.save(baos);</span>
    +<span class="nc" id="L329">                return new ByteArrayInputStream(baos.toByteArray());</span>
    +            }
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/GetInfoOnPDF.html b/testResult/html/stirling.software.SPDF.controller.api.security/GetInfoOnPDF.html
    new file mode 100644
    index 000000000..78036a266
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/GetInfoOnPDF.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GetInfoOnPDF</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">GetInfoOnPDF</span></div><h1>GetInfoOnPDF</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,924 of 1,924</td><td class="ctr2">0%</td><td class="bar">162 of 162</td><td class="ctr2">0%</td><td class="ctr1">96</td><td class="ctr2">96</td><td class="ctr1">418</td><td class="ctr2">418</td><td class="ctr1">15</td><td class="ctr2">15</td></tr></tfoot><tbody><tr><td id="a10"><a href="GetInfoOnPDF.java.html#L128" class="el_method">getPdfInfo(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,428" alt="1,428"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="120" alt="120"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">61</td><td class="ctr2" id="g0">61</td><td class="ctr1" id="h0">316</td><td class="ctr2" id="i0">316</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="GetInfoOnPDF.java.html#L681" class="el_method">getDimensionInfo(ObjectNode, float, float)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="98" alt="98"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h4">12</td><td class="ctr2" id="i4">12</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a9"><a href="GetInfoOnPDF.java.html#L653" class="el_method">getPageSize(float, float)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="80" alt="80"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h1">16</td><td class="ctr2" id="i1">16</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="GetInfoOnPDF.java.html#L699" class="el_method">exploreStructureTree(List)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="62" alt="62"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="10" alt="10"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h2">14</td><td class="ctr2" id="i2">14</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a13"><a href="GetInfoOnPDF.java.html#L624" class="el_method">setNodePermissions(PDDocument, ObjectNode)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="60" alt="60"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h5">10</td><td class="ctr2" id="i5">10</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="GetInfoOnPDF.java.html#L101" class="el_method">checkForStandard(PDDocument, String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="49" alt="49"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h3">14</td><td class="ctr2" id="i3">14</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="GetInfoOnPDF.java.html#L723" class="el_method">getContent(PDStructureElement)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="40" alt="40"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="6" alt="6"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h7">8</td><td class="ctr2" id="i7">8</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a0"><a href="GetInfoOnPDF.java.html#L84" class="el_method">addOutlinesToArray(PDOutlineItem, ArrayNode)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="29" alt="29"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h6">9</td><td class="ctr2" id="i6">9</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a12"><a href="GetInfoOnPDF.java.html#L675" class="el_method">isCloseToSize(float, float, float, float)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="20" alt="20"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">3</td><td class="ctr2" id="g6">3</td><td class="ctr1" id="h10">3</td><td class="ctr2" id="i10">3</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a8"><a href="GetInfoOnPDF.java.html#L642" class="el_method">getPageOrientation(double, double)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="14" alt="14"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">3</td><td class="ctr2" id="g7">3</td><td class="ctr1" id="h8">5</td><td class="ctr2" id="i8">5</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a3"><a href="GetInfoOnPDF.java.html#L739" class="el_method">formatDate(Calendar)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="14" alt="14"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h9">4</td><td class="ctr2" id="i9">4</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a7"><a href="GetInfoOnPDF.java.html#L748" class="el_method">getPageModeDescription(String)</a></td><td class="bar" id="b11"/><td class="ctr2" id="c11">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h13">1</td><td class="ctr2" id="i13">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a14"><a href="GetInfoOnPDF.java.html#L70" class="el_method">static {...}</a></td><td class="bar" id="b12"/><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h12">2</td><td class="ctr2" id="i12">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a6"><a href="GetInfoOnPDF.java.html#L79" class="el_method">GetInfoOnPDF(CustomPDFDocumentFactory)</a></td><td class="bar" id="b13"/><td class="ctr2" id="c13">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h11">3</td><td class="ctr2" id="i11">3</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a11"><a href="GetInfoOnPDF.java.html#L638" class="el_method">getPermissionState(boolean)</a></td><td class="bar" id="b14"/><td class="ctr2" id="c14">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h14">1</td><td class="ctr2" id="i14">1</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/GetInfoOnPDF.java.html b/testResult/html/stirling.software.SPDF.controller.api.security/GetInfoOnPDF.java.html
    new file mode 100644
    index 000000000..1be8deda7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/GetInfoOnPDF.java.html
    @@ -0,0 +1,751 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GetInfoOnPDF.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_source">GetInfoOnPDF.java</span></div><h1>GetInfoOnPDF.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.security;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.nio.charset.StandardCharsets;
    +import java.text.SimpleDateFormat;
    +import java.util.*;
    +
    +import org.apache.pdfbox.cos.COSInputStream;
    +import org.apache.pdfbox.cos.COSName;
    +import org.apache.pdfbox.cos.COSString;
    +import org.apache.pdfbox.pdmodel.*;
    +import org.apache.pdfbox.pdmodel.common.PDMetadata;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.common.PDStream;
    +import org.apache.pdfbox.pdmodel.common.filespecification.PDComplexFileSpecification;
    +import org.apache.pdfbox.pdmodel.common.filespecification.PDEmbeddedFile;
    +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
    +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureNode;
    +import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureTreeRoot;
    +import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
    +import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
    +import org.apache.pdfbox.pdmodel.font.PDFont;
    +import org.apache.pdfbox.pdmodel.font.PDFontDescriptor;
    +import org.apache.pdfbox.pdmodel.graphics.PDXObject;
    +import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
    +import org.apache.pdfbox.pdmodel.graphics.color.PDICCBased;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
    +import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentProperties;
    +import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
    +import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationFileAttachment;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
    +import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
    +import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineNode;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDField;
    +import org.apache.pdfbox.text.PDFTextStripper;
    +import org.apache.xmpbox.XMPMetadata;
    +import org.apache.xmpbox.xml.DomXmpParser;
    +import org.apache.xmpbox.xml.XmpParsingException;
    +import org.apache.xmpbox.xml.XmpSerializer;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +import com.fasterxml.jackson.databind.node.ArrayNode;
    +import com.fasterxml.jackson.databind.node.ObjectNode;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/security&quot;)
    +<span class="nc" id="L70">@Slf4j</span>
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +public class GetInfoOnPDF {
    +
    +<span class="nc" id="L74">    static ObjectMapper objectMapper = new ObjectMapper();</span>
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L79">    public GetInfoOnPDF(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L80">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L81">    }</span>
    +
    +    private static void addOutlinesToArray(PDOutlineItem outline, ArrayNode arrayNode) {
    +<span class="nc bnc" id="L84" title="All 2 branches missed.">        if (outline == null) return;</span>
    +
    +<span class="nc" id="L86">        ObjectNode outlineNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L87">        outlineNode.put(&quot;Title&quot;, outline.getTitle());</span>
    +        // You can add other properties if needed
    +<span class="nc" id="L89">        arrayNode.add(outlineNode);</span>
    +
    +<span class="nc" id="L91">        PDOutlineItem child = outline.getFirstChild();</span>
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">        while (child != null) {</span>
    +<span class="nc" id="L93">            addOutlinesToArray(child, arrayNode);</span>
    +<span class="nc" id="L94">            child = child.getNextSibling();</span>
    +        }
    +<span class="nc" id="L96">    }</span>
    +
    +    public static boolean checkForStandard(PDDocument document, String standardKeyword) {
    +        // Check XMP Metadata
    +        try {
    +<span class="nc" id="L101">            PDMetadata pdMetadata = document.getDocumentCatalog().getMetadata();</span>
    +<span class="nc bnc" id="L102" title="All 2 branches missed.">            if (pdMetadata != null) {</span>
    +<span class="nc" id="L103">                COSInputStream metaStream = pdMetadata.createInputStream();</span>
    +<span class="nc" id="L104">                DomXmpParser domXmpParser = new DomXmpParser();</span>
    +<span class="nc" id="L105">                XMPMetadata xmpMeta = domXmpParser.parse(metaStream);</span>
    +
    +<span class="nc" id="L107">                ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L108">                new XmpSerializer().serialize(xmpMeta, baos, true);</span>
    +<span class="nc" id="L109">                String xmpString = new String(baos.toByteArray(), StandardCharsets.UTF_8);</span>
    +
    +<span class="nc bnc" id="L111" title="All 2 branches missed.">                if (xmpString.contains(standardKeyword)) {</span>
    +<span class="nc" id="L112">                    return true;</span>
    +                }
    +            }
    +<span class="nc" id="L115">        } catch (</span>
    +                Exception
    +                        e) { // Catching general exception for brevity, ideally you'd catch specific
    +            // exceptions.
    +<span class="nc" id="L119">            log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L120">        }</span>
    +
    +<span class="nc" id="L122">        return false;</span>
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/get-info-on-pdf&quot;)
    +    @Operation(summary = &quot;Summary here&quot;, description = &quot;desc. Input:PDF Output:JSON Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
    +<span class="nc" id="L128">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L129">        boolean readonly = true;</span>
    +<span class="nc" id="L130">        try (PDDocument pdfBoxDoc = pdfDocumentFactory.load(inputFile, readonly); ) {</span>
    +<span class="nc" id="L131">            ObjectMapper objectMapper = new ObjectMapper();</span>
    +<span class="nc" id="L132">            ObjectNode jsonOutput = objectMapper.createObjectNode();</span>
    +
    +            // Metadata using PDFBox
    +<span class="nc" id="L135">            PDDocumentInformation info = pdfBoxDoc.getDocumentInformation();</span>
    +<span class="nc" id="L136">            ObjectNode metadata = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L137">            ObjectNode basicInfo = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L138">            ObjectNode docInfoNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L139">            ObjectNode compliancy = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L140">            ObjectNode encryption = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L141">            ObjectNode other = objectMapper.createObjectNode();</span>
    +
    +<span class="nc" id="L143">            metadata.put(&quot;Title&quot;, info.getTitle());</span>
    +<span class="nc" id="L144">            metadata.put(&quot;Author&quot;, info.getAuthor());</span>
    +<span class="nc" id="L145">            metadata.put(&quot;Subject&quot;, info.getSubject());</span>
    +<span class="nc" id="L146">            metadata.put(&quot;Keywords&quot;, info.getKeywords());</span>
    +<span class="nc" id="L147">            metadata.put(&quot;Producer&quot;, info.getProducer());</span>
    +<span class="nc" id="L148">            metadata.put(&quot;Creator&quot;, info.getCreator());</span>
    +<span class="nc" id="L149">            metadata.put(&quot;CreationDate&quot;, formatDate(info.getCreationDate()));</span>
    +<span class="nc" id="L150">            metadata.put(&quot;ModificationDate&quot;, formatDate(info.getModificationDate()));</span>
    +<span class="nc" id="L151">            jsonOutput.set(&quot;Metadata&quot;, metadata);</span>
    +
    +            // Total file size of the PDF
    +<span class="nc" id="L154">            long fileSizeInBytes = inputFile.getSize();</span>
    +<span class="nc" id="L155">            basicInfo.put(&quot;FileSizeInBytes&quot;, fileSizeInBytes);</span>
    +
    +            // Number of words, paragraphs, and images in the entire document
    +<span class="nc" id="L158">            String fullText = new PDFTextStripper().getText(pdfBoxDoc);</span>
    +<span class="nc" id="L159">            String[] words = fullText.split(&quot;\\s+&quot;);</span>
    +<span class="nc" id="L160">            int wordCount = words.length;</span>
    +<span class="nc" id="L161">            int paragraphCount = fullText.split(&quot;\r\n|\r|\n&quot;).length;</span>
    +<span class="nc" id="L162">            basicInfo.put(&quot;WordCount&quot;, wordCount);</span>
    +<span class="nc" id="L163">            basicInfo.put(&quot;ParagraphCount&quot;, paragraphCount);</span>
    +            // Number of characters in the entire document (including spaces and special characters)
    +<span class="nc" id="L165">            int charCount = fullText.length();</span>
    +<span class="nc" id="L166">            basicInfo.put(&quot;CharacterCount&quot;, charCount);</span>
    +
    +            // Initialize the flags and types
    +<span class="nc" id="L169">            boolean hasCompression = false;</span>
    +<span class="nc" id="L170">            String compressionType = &quot;None&quot;;</span>
    +
    +<span class="nc" id="L172">            basicInfo.put(&quot;Compression&quot;, hasCompression);</span>
    +<span class="nc bnc" id="L173" title="All 2 branches missed.">            if (hasCompression) basicInfo.put(&quot;CompressionType&quot;, compressionType);</span>
    +
    +<span class="nc" id="L175">            String language = pdfBoxDoc.getDocumentCatalog().getLanguage();</span>
    +<span class="nc" id="L176">            basicInfo.put(&quot;Language&quot;, language);</span>
    +<span class="nc" id="L177">            basicInfo.put(&quot;Number of pages&quot;, pdfBoxDoc.getNumberOfPages());</span>
    +
    +<span class="nc" id="L179">            PDDocumentCatalog catalog = pdfBoxDoc.getDocumentCatalog();</span>
    +<span class="nc" id="L180">            String pageMode = catalog.getPageMode().name();</span>
    +
    +            // Document Information using PDFBox
    +<span class="nc" id="L183">            docInfoNode.put(&quot;PDF version&quot;, pdfBoxDoc.getVersion());</span>
    +<span class="nc" id="L184">            docInfoNode.put(&quot;Trapped&quot;, info.getTrapped());</span>
    +<span class="nc" id="L185">            docInfoNode.put(&quot;Page Mode&quot;, getPageModeDescription(pageMode));</span>
    +            ;
    +
    +<span class="nc" id="L188">            PDAcroForm acroForm = pdfBoxDoc.getDocumentCatalog().getAcroForm();</span>
    +
    +<span class="nc" id="L190">            ObjectNode formFieldsNode = objectMapper.createObjectNode();</span>
    +<span class="nc bnc" id="L191" title="All 2 branches missed.">            if (acroForm != null) {</span>
    +<span class="nc bnc" id="L192" title="All 2 branches missed.">                for (PDField field : acroForm.getFieldTree()) {</span>
    +<span class="nc" id="L193">                    formFieldsNode.put(field.getFullyQualifiedName(), field.getValueAsString());</span>
    +<span class="nc" id="L194">                }</span>
    +            }
    +<span class="nc" id="L196">            jsonOutput.set(&quot;FormFields&quot;, formFieldsNode);</span>
    +
    +            // embeed files TODO size
    +<span class="nc bnc" id="L199" title="All 2 branches missed.">            if (catalog.getNames() != null) {</span>
    +<span class="nc" id="L200">                PDEmbeddedFilesNameTreeNode efTree = catalog.getNames().getEmbeddedFiles();</span>
    +
    +<span class="nc" id="L202">                ArrayNode embeddedFilesArray = objectMapper.createArrayNode();</span>
    +<span class="nc bnc" id="L203" title="All 2 branches missed.">                if (efTree != null) {</span>
    +<span class="nc" id="L204">                    Map&lt;String, PDComplexFileSpecification&gt; efMap = efTree.getNames();</span>
    +<span class="nc bnc" id="L205" title="All 2 branches missed.">                    if (efMap != null) {</span>
    +                        for (Map.Entry&lt;String, PDComplexFileSpecification&gt; entry :
    +<span class="nc bnc" id="L207" title="All 2 branches missed.">                                efMap.entrySet()) {</span>
    +<span class="nc" id="L208">                            ObjectNode embeddedFileNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L209">                            embeddedFileNode.put(&quot;Name&quot;, entry.getKey());</span>
    +<span class="nc" id="L210">                            PDEmbeddedFile embeddedFile = entry.getValue().getEmbeddedFile();</span>
    +<span class="nc bnc" id="L211" title="All 2 branches missed.">                            if (embeddedFile != null) {</span>
    +<span class="nc" id="L212">                                embeddedFileNode.put(</span>
    +<span class="nc" id="L213">                                        &quot;FileSize&quot;, embeddedFile.getLength()); // size in bytes</span>
    +                            }
    +<span class="nc" id="L215">                            embeddedFilesArray.add(embeddedFileNode);</span>
    +<span class="nc" id="L216">                        }</span>
    +                    }
    +                }
    +<span class="nc" id="L219">                other.set(&quot;EmbeddedFiles&quot;, embeddedFilesArray);</span>
    +            }
    +
    +            // attachments TODO size
    +<span class="nc" id="L223">            ArrayNode attachmentsArray = objectMapper.createArrayNode();</span>
    +<span class="nc bnc" id="L224" title="All 2 branches missed.">            for (PDPage page : pdfBoxDoc.getPages()) {</span>
    +<span class="nc bnc" id="L225" title="All 2 branches missed.">                for (PDAnnotation annotation : page.getAnnotations()) {</span>
    +<span class="nc bnc" id="L226" title="All 2 branches missed.">                    if (annotation instanceof PDAnnotationFileAttachment fileAttachmentAnnotation) {</span>
    +<span class="nc" id="L227">                        ObjectNode attachmentNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L228">                        attachmentNode.put(&quot;Name&quot;, fileAttachmentAnnotation.getAttachmentName());</span>
    +<span class="nc" id="L229">                        attachmentNode.put(&quot;Description&quot;, fileAttachmentAnnotation.getContents());</span>
    +
    +<span class="nc" id="L231">                        attachmentsArray.add(attachmentNode);</span>
    +                    }
    +<span class="nc" id="L233">                }</span>
    +<span class="nc" id="L234">            }</span>
    +<span class="nc" id="L235">            other.set(&quot;Attachments&quot;, attachmentsArray);</span>
    +
    +            // Javascript
    +<span class="nc" id="L238">            PDDocumentNameDictionary namesDict = catalog.getNames();</span>
    +<span class="nc" id="L239">            ArrayNode javascriptArray = objectMapper.createArrayNode();</span>
    +
    +<span class="nc bnc" id="L241" title="All 2 branches missed.">            if (namesDict != null) {</span>
    +<span class="nc" id="L242">                PDJavascriptNameTreeNode javascriptDict = namesDict.getJavaScript();</span>
    +<span class="nc bnc" id="L243" title="All 2 branches missed.">                if (javascriptDict != null) {</span>
    +                    try {
    +<span class="nc" id="L245">                        Map&lt;String, PDActionJavaScript&gt; jsEntries = javascriptDict.getNames();</span>
    +
    +<span class="nc bnc" id="L247" title="All 2 branches missed.">                        for (Map.Entry&lt;String, PDActionJavaScript&gt; entry : jsEntries.entrySet()) {</span>
    +<span class="nc" id="L248">                            ObjectNode jsNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L249">                            jsNode.put(&quot;JS Name&quot;, entry.getKey());</span>
    +
    +<span class="nc" id="L251">                            PDActionJavaScript jsAction = entry.getValue();</span>
    +<span class="nc bnc" id="L252" title="All 2 branches missed.">                            if (jsAction != null) {</span>
    +<span class="nc" id="L253">                                String jsCodeStr = jsAction.getAction();</span>
    +<span class="nc bnc" id="L254" title="All 2 branches missed.">                                if (jsCodeStr != null) {</span>
    +<span class="nc" id="L255">                                    jsNode.put(&quot;JS Script Length&quot;, jsCodeStr.length());</span>
    +                                }
    +                            }
    +
    +<span class="nc" id="L259">                            javascriptArray.add(jsNode);</span>
    +<span class="nc" id="L260">                        }</span>
    +<span class="nc" id="L261">                    } catch (IOException e) {</span>
    +<span class="nc" id="L262">                        log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L263">                    }</span>
    +                }
    +            }
    +<span class="nc" id="L266">            other.set(&quot;JavaScript&quot;, javascriptArray);</span>
    +
    +            // TODO size
    +<span class="nc" id="L269">            PDOptionalContentProperties ocProperties =</span>
    +<span class="nc" id="L270">                    pdfBoxDoc.getDocumentCatalog().getOCProperties();</span>
    +<span class="nc" id="L271">            ArrayNode layersArray = objectMapper.createArrayNode();</span>
    +
    +<span class="nc bnc" id="L273" title="All 2 branches missed.">            if (ocProperties != null) {</span>
    +<span class="nc bnc" id="L274" title="All 2 branches missed.">                for (PDOptionalContentGroup ocg : ocProperties.getOptionalContentGroups()) {</span>
    +<span class="nc" id="L275">                    ObjectNode layerNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L276">                    layerNode.put(&quot;Name&quot;, ocg.getName());</span>
    +<span class="nc" id="L277">                    layersArray.add(layerNode);</span>
    +<span class="nc" id="L278">                }</span>
    +            }
    +
    +<span class="nc" id="L281">            other.set(&quot;Layers&quot;, layersArray);</span>
    +
    +            // TODO Security
    +
    +<span class="nc" id="L285">            PDStructureTreeRoot structureTreeRoot =</span>
    +<span class="nc" id="L286">                    pdfBoxDoc.getDocumentCatalog().getStructureTreeRoot();</span>
    +            ArrayNode structureTreeArray;
    +            try {
    +<span class="nc bnc" id="L289" title="All 2 branches missed.">                if (structureTreeRoot != null) {</span>
    +<span class="nc" id="L290">                    structureTreeArray = exploreStructureTree(structureTreeRoot.getKids());</span>
    +<span class="nc" id="L291">                    other.set(&quot;StructureTree&quot;, structureTreeArray);</span>
    +                }
    +<span class="nc" id="L293">            } catch (Exception e) {</span>
    +                // TODO Auto-generated catch block
    +<span class="nc" id="L295">                log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L296">            }</span>
    +
    +<span class="nc" id="L298">            boolean isPdfACompliant = checkForStandard(pdfBoxDoc, &quot;PDF/A&quot;);</span>
    +<span class="nc" id="L299">            boolean isPdfXCompliant = checkForStandard(pdfBoxDoc, &quot;PDF/X&quot;);</span>
    +<span class="nc" id="L300">            boolean isPdfECompliant = checkForStandard(pdfBoxDoc, &quot;PDF/E&quot;);</span>
    +<span class="nc" id="L301">            boolean isPdfVTCompliant = checkForStandard(pdfBoxDoc, &quot;PDF/VT&quot;);</span>
    +<span class="nc" id="L302">            boolean isPdfUACompliant = checkForStandard(pdfBoxDoc, &quot;PDF/UA&quot;);</span>
    +<span class="nc" id="L303">            boolean isPdfBCompliant =</span>
    +<span class="nc" id="L304">                    checkForStandard(</span>
    +                            pdfBoxDoc,
    +                            &quot;PDF/B&quot;); // If you want to check for PDF/Broadcast, though this isn't
    +            // an official ISO standard.
    +<span class="nc" id="L308">            boolean isPdfSECCompliant =</span>
    +<span class="nc" id="L309">                    checkForStandard(</span>
    +                            pdfBoxDoc,
    +                            &quot;PDF/SEC&quot;); // This might not be effective since PDF/SEC was under
    +            // development in 2021.
    +
    +<span class="nc" id="L314">            compliancy.put(&quot;IsPDF/ACompliant&quot;, isPdfACompliant);</span>
    +<span class="nc" id="L315">            compliancy.put(&quot;IsPDF/XCompliant&quot;, isPdfXCompliant);</span>
    +<span class="nc" id="L316">            compliancy.put(&quot;IsPDF/ECompliant&quot;, isPdfECompliant);</span>
    +<span class="nc" id="L317">            compliancy.put(&quot;IsPDF/VTCompliant&quot;, isPdfVTCompliant);</span>
    +<span class="nc" id="L318">            compliancy.put(&quot;IsPDF/UACompliant&quot;, isPdfUACompliant);</span>
    +<span class="nc" id="L319">            compliancy.put(&quot;IsPDF/BCompliant&quot;, isPdfBCompliant);</span>
    +<span class="nc" id="L320">            compliancy.put(&quot;IsPDF/SECCompliant&quot;, isPdfSECCompliant);</span>
    +
    +<span class="nc" id="L322">            PDOutlineNode root = pdfBoxDoc.getDocumentCatalog().getDocumentOutline();</span>
    +<span class="nc" id="L323">            ArrayNode bookmarksArray = objectMapper.createArrayNode();</span>
    +
    +<span class="nc bnc" id="L325" title="All 2 branches missed.">            if (root != null) {</span>
    +<span class="nc bnc" id="L326" title="All 2 branches missed.">                for (PDOutlineItem child : root.children()) {</span>
    +<span class="nc" id="L327">                    addOutlinesToArray(child, bookmarksArray);</span>
    +<span class="nc" id="L328">                }</span>
    +            }
    +
    +<span class="nc" id="L331">            other.set(&quot;Bookmarks/Outline/TOC&quot;, bookmarksArray);</span>
    +
    +<span class="nc" id="L333">            PDMetadata pdMetadata = pdfBoxDoc.getDocumentCatalog().getMetadata();</span>
    +
    +<span class="nc" id="L335">            String xmpString = null;</span>
    +
    +<span class="nc bnc" id="L337" title="All 2 branches missed.">            if (pdMetadata != null) {</span>
    +                try {
    +<span class="nc" id="L339">                    COSInputStream is = pdMetadata.createInputStream();</span>
    +<span class="nc" id="L340">                    DomXmpParser domXmpParser = new DomXmpParser();</span>
    +<span class="nc" id="L341">                    XMPMetadata xmpMeta = domXmpParser.parse(is);</span>
    +
    +<span class="nc" id="L343">                    ByteArrayOutputStream os = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L344">                    new XmpSerializer().serialize(xmpMeta, os, true);</span>
    +<span class="nc" id="L345">                    xmpString = new String(os.toByteArray(), StandardCharsets.UTF_8);</span>
    +<span class="nc" id="L346">                } catch (XmpParsingException | IOException e) {</span>
    +<span class="nc" id="L347">                    log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L348">                }</span>
    +            }
    +
    +<span class="nc" id="L351">            other.put(&quot;XMPMetadata&quot;, xmpString);</span>
    +
    +<span class="nc bnc" id="L353" title="All 2 branches missed.">            if (pdfBoxDoc.isEncrypted()) {</span>
    +<span class="nc" id="L354">                encryption.put(&quot;IsEncrypted&quot;, true);</span>
    +
    +                // Retrieve encryption details using getEncryption()
    +<span class="nc" id="L357">                PDEncryption pdfEncryption = pdfBoxDoc.getEncryption();</span>
    +<span class="nc" id="L358">                encryption.put(&quot;EncryptionAlgorithm&quot;, pdfEncryption.getFilter());</span>
    +<span class="nc" id="L359">                encryption.put(&quot;KeyLength&quot;, pdfEncryption.getLength());</span>
    +                // Add other encryption-related properties as needed
    +<span class="nc" id="L361">            } else {</span>
    +<span class="nc" id="L362">                encryption.put(&quot;IsEncrypted&quot;, false);</span>
    +            }
    +
    +<span class="nc" id="L365">            ObjectNode permissionsNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L366">            setNodePermissions(pdfBoxDoc, permissionsNode);</span>
    +
    +<span class="nc" id="L368">            ObjectNode pageInfoParent = objectMapper.createObjectNode();</span>
    +<span class="nc bnc" id="L369" title="All 2 branches missed.">            for (int pageNum = 0; pageNum &lt; pdfBoxDoc.getNumberOfPages(); pageNum++) {</span>
    +<span class="nc" id="L370">                ObjectNode pageInfo = objectMapper.createObjectNode();</span>
    +
    +                // Retrieve the page
    +<span class="nc" id="L373">                PDPage page = pdfBoxDoc.getPage(pageNum);</span>
    +
    +                // Page-level Information
    +<span class="nc" id="L376">                PDRectangle mediaBox = page.getMediaBox();</span>
    +
    +<span class="nc" id="L378">                float width = mediaBox.getWidth();</span>
    +<span class="nc" id="L379">                float height = mediaBox.getHeight();</span>
    +
    +<span class="nc" id="L381">                ObjectNode sizeInfo = objectMapper.createObjectNode();</span>
    +
    +<span class="nc" id="L383">                getDimensionInfo(sizeInfo, width, height);</span>
    +
    +<span class="nc" id="L385">                sizeInfo.put(&quot;Standard Page&quot;, getPageSize(width, height));</span>
    +<span class="nc" id="L386">                pageInfo.set(&quot;Size&quot;, sizeInfo);</span>
    +
    +<span class="nc" id="L388">                pageInfo.put(&quot;Rotation&quot;, page.getRotation());</span>
    +<span class="nc" id="L389">                pageInfo.put(&quot;Page Orientation&quot;, getPageOrientation(width, height));</span>
    +
    +                // Boxes
    +<span class="nc" id="L392">                pageInfo.put(&quot;MediaBox&quot;, mediaBox.toString());</span>
    +
    +                // Assuming the following boxes are defined for your document; if not, you may get
    +                // null values.
    +<span class="nc" id="L396">                PDRectangle cropBox = page.getCropBox();</span>
    +<span class="nc bnc" id="L397" title="All 2 branches missed.">                pageInfo.put(&quot;CropBox&quot;, cropBox == null ? &quot;Undefined&quot; : cropBox.toString());</span>
    +
    +<span class="nc" id="L399">                PDRectangle bleedBox = page.getBleedBox();</span>
    +<span class="nc bnc" id="L400" title="All 2 branches missed.">                pageInfo.put(&quot;BleedBox&quot;, bleedBox == null ? &quot;Undefined&quot; : bleedBox.toString());</span>
    +
    +<span class="nc" id="L402">                PDRectangle trimBox = page.getTrimBox();</span>
    +<span class="nc bnc" id="L403" title="All 2 branches missed.">                pageInfo.put(&quot;TrimBox&quot;, trimBox == null ? &quot;Undefined&quot; : trimBox.toString());</span>
    +
    +<span class="nc" id="L405">                PDRectangle artBox = page.getArtBox();</span>
    +<span class="nc bnc" id="L406" title="All 2 branches missed.">                pageInfo.put(&quot;ArtBox&quot;, artBox == null ? &quot;Undefined&quot; : artBox.toString());</span>
    +
    +                // Content Extraction
    +<span class="nc" id="L409">                PDFTextStripper textStripper = new PDFTextStripper();</span>
    +<span class="nc" id="L410">                textStripper.setStartPage(pageNum + 1);</span>
    +<span class="nc" id="L411">                textStripper.setEndPage(pageNum + 1);</span>
    +<span class="nc" id="L412">                String pageText = textStripper.getText(pdfBoxDoc);</span>
    +
    +<span class="nc" id="L414">                pageInfo.put(&quot;Text Characters Count&quot;, pageText.length()); //</span>
    +
    +                // Annotations
    +
    +<span class="nc" id="L418">                List&lt;PDAnnotation&gt; annotations = page.getAnnotations();</span>
    +
    +<span class="nc" id="L420">                int subtypeCount = 0;</span>
    +<span class="nc" id="L421">                int contentsCount = 0;</span>
    +
    +<span class="nc bnc" id="L423" title="All 2 branches missed.">                for (PDAnnotation annotation : annotations) {</span>
    +<span class="nc bnc" id="L424" title="All 2 branches missed.">                    if (annotation.getSubtype() != null) {</span>
    +<span class="nc" id="L425">                        subtypeCount++; // Increase subtype count</span>
    +                    }
    +<span class="nc bnc" id="L427" title="All 2 branches missed.">                    if (annotation.getContents() != null) {</span>
    +<span class="nc" id="L428">                        contentsCount++; // Increase contents count</span>
    +                    }
    +<span class="nc" id="L430">                }</span>
    +
    +<span class="nc" id="L432">                ObjectNode annotationsObject = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L433">                annotationsObject.put(&quot;AnnotationsCount&quot;, annotations.size());</span>
    +<span class="nc" id="L434">                annotationsObject.put(&quot;SubtypeCount&quot;, subtypeCount);</span>
    +<span class="nc" id="L435">                annotationsObject.put(&quot;ContentsCount&quot;, contentsCount);</span>
    +<span class="nc" id="L436">                pageInfo.set(&quot;Annotations&quot;, annotationsObject);</span>
    +
    +                // Images (simplified)
    +                // This part is non-trivial as images can be embedded in multiple ways in a PDF.
    +                // Here is a basic structure to recognize image XObjects on a page.
    +<span class="nc" id="L441">                ArrayNode imagesArray = objectMapper.createArrayNode();</span>
    +<span class="nc" id="L442">                PDResources resources = page.getResources();</span>
    +
    +<span class="nc bnc" id="L444" title="All 2 branches missed.">                for (COSName name : resources.getXObjectNames()) {</span>
    +<span class="nc" id="L445">                    PDXObject xObject = resources.getXObject(name);</span>
    +<span class="nc bnc" id="L446" title="All 2 branches missed.">                    if (xObject instanceof PDImageXObject image) {</span>
    +<span class="nc" id="L447">                        ObjectNode imageNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L448">                        imageNode.put(&quot;Width&quot;, image.getWidth());</span>
    +<span class="nc" id="L449">                        imageNode.put(&quot;Height&quot;, image.getHeight());</span>
    +<span class="nc bnc" id="L450" title="All 2 branches missed.">                        if (image.getMetadata() != null</span>
    +<span class="nc bnc" id="L451" title="All 2 branches missed.">                                &amp;&amp; image.getMetadata().getFile() != null</span>
    +<span class="nc bnc" id="L452" title="All 2 branches missed.">                                &amp;&amp; image.getMetadata().getFile().getFile() != null) {</span>
    +<span class="nc" id="L453">                            imageNode.put(&quot;Name&quot;, image.getMetadata().getFile().getFile());</span>
    +                        }
    +<span class="nc bnc" id="L455" title="All 2 branches missed.">                        if (image.getColorSpace() != null) {</span>
    +<span class="nc" id="L456">                            imageNode.put(&quot;ColorSpace&quot;, image.getColorSpace().getName());</span>
    +                        }
    +
    +<span class="nc" id="L459">                        imagesArray.add(imageNode);</span>
    +                    }
    +<span class="nc" id="L461">                }</span>
    +<span class="nc" id="L462">                pageInfo.set(&quot;Images&quot;, imagesArray);</span>
    +
    +                // Links
    +<span class="nc" id="L465">                ArrayNode linksArray = objectMapper.createArrayNode();</span>
    +<span class="nc" id="L466">                Set&lt;String&gt; uniqueURIs = new HashSet&lt;&gt;(); // To store unique URIs</span>
    +
    +<span class="nc bnc" id="L468" title="All 2 branches missed.">                for (PDAnnotation annotation : annotations) {</span>
    +<span class="nc bnc" id="L469" title="All 2 branches missed.">                    if (annotation instanceof PDAnnotationLink linkAnnotation) {</span>
    +<span class="nc bnc" id="L470" title="All 2 branches missed.">                        if (linkAnnotation.getAction() instanceof PDActionURI uriAction) {</span>
    +<span class="nc" id="L471">                            String uri = uriAction.getURI();</span>
    +<span class="nc" id="L472">                            uniqueURIs.add(uri); // Add to set to ensure uniqueness</span>
    +                        }
    +                    }
    +<span class="nc" id="L475">                }</span>
    +
    +                // Add unique URIs to linksArray
    +<span class="nc bnc" id="L478" title="All 2 branches missed.">                for (String uri : uniqueURIs) {</span>
    +<span class="nc" id="L479">                    ObjectNode linkNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L480">                    linkNode.put(&quot;URI&quot;, uri);</span>
    +<span class="nc" id="L481">                    linksArray.add(linkNode);</span>
    +<span class="nc" id="L482">                }</span>
    +<span class="nc" id="L483">                pageInfo.set(&quot;Links&quot;, linksArray);</span>
    +
    +                // Fonts
    +<span class="nc" id="L486">                ArrayNode fontsArray = objectMapper.createArrayNode();</span>
    +<span class="nc" id="L487">                Map&lt;String, ObjectNode&gt; uniqueFontsMap = new HashMap&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L489" title="All 2 branches missed.">                for (COSName fontName : resources.getFontNames()) {</span>
    +<span class="nc" id="L490">                    PDFont font = resources.getFont(fontName);</span>
    +<span class="nc" id="L491">                    ObjectNode fontNode = objectMapper.createObjectNode();</span>
    +
    +<span class="nc" id="L493">                    fontNode.put(&quot;IsEmbedded&quot;, font.isEmbedded());</span>
    +
    +                    // PDFBox provides Font's BaseFont (i.e., the font name) directly
    +<span class="nc" id="L496">                    fontNode.put(&quot;Name&quot;, font.getName());</span>
    +
    +<span class="nc" id="L498">                    fontNode.put(&quot;Subtype&quot;, font.getType());</span>
    +
    +<span class="nc" id="L500">                    PDFontDescriptor fontDescriptor = font.getFontDescriptor();</span>
    +
    +<span class="nc bnc" id="L502" title="All 2 branches missed.">                    if (fontDescriptor != null) {</span>
    +<span class="nc" id="L503">                        fontNode.put(&quot;ItalicAngle&quot;, fontDescriptor.getItalicAngle());</span>
    +<span class="nc" id="L504">                        int flags = fontDescriptor.getFlags();</span>
    +<span class="nc bnc" id="L505" title="All 2 branches missed.">                        fontNode.put(&quot;IsItalic&quot;, (flags &amp; 1) != 0);</span>
    +<span class="nc bnc" id="L506" title="All 2 branches missed.">                        fontNode.put(&quot;IsBold&quot;, (flags &amp; 64) != 0);</span>
    +<span class="nc bnc" id="L507" title="All 2 branches missed.">                        fontNode.put(&quot;IsFixedPitch&quot;, (flags &amp; 2) != 0);</span>
    +<span class="nc bnc" id="L508" title="All 2 branches missed.">                        fontNode.put(&quot;IsSerif&quot;, (flags &amp; 4) != 0);</span>
    +<span class="nc bnc" id="L509" title="All 2 branches missed.">                        fontNode.put(&quot;IsSymbolic&quot;, (flags &amp; 8) != 0);</span>
    +<span class="nc bnc" id="L510" title="All 2 branches missed.">                        fontNode.put(&quot;IsScript&quot;, (flags &amp; 16) != 0);</span>
    +<span class="nc bnc" id="L511" title="All 2 branches missed.">                        fontNode.put(&quot;IsNonsymbolic&quot;, (flags &amp; 32) != 0);</span>
    +
    +<span class="nc" id="L513">                        fontNode.put(&quot;FontFamily&quot;, fontDescriptor.getFontFamily());</span>
    +                        // Font stretch and BBox are not directly available in PDFBox's API, so
    +                        // these are omitted for simplicity
    +<span class="nc" id="L516">                        fontNode.put(&quot;FontWeight&quot;, fontDescriptor.getFontWeight());</span>
    +                    }
    +
    +                    // Create a unique key for this font node based on its attributes
    +<span class="nc" id="L520">                    String uniqueKey = fontNode.toString();</span>
    +
    +                    // Increment count if this font exists, or initialize it if new
    +<span class="nc bnc" id="L523" title="All 2 branches missed.">                    if (uniqueFontsMap.containsKey(uniqueKey)) {</span>
    +<span class="nc" id="L524">                        ObjectNode existingFontNode = uniqueFontsMap.get(uniqueKey);</span>
    +<span class="nc" id="L525">                        int count = existingFontNode.get(&quot;Count&quot;).asInt() + 1;</span>
    +<span class="nc" id="L526">                        existingFontNode.put(&quot;Count&quot;, count);</span>
    +<span class="nc" id="L527">                    } else {</span>
    +<span class="nc" id="L528">                        fontNode.put(&quot;Count&quot;, 1);</span>
    +<span class="nc" id="L529">                        uniqueFontsMap.put(uniqueKey, fontNode);</span>
    +                    }
    +<span class="nc" id="L531">                }</span>
    +
    +                // Add unique font entries to fontsArray
    +<span class="nc bnc" id="L534" title="All 2 branches missed.">                for (ObjectNode uniqueFontNode : uniqueFontsMap.values()) {</span>
    +<span class="nc" id="L535">                    fontsArray.add(uniqueFontNode);</span>
    +<span class="nc" id="L536">                }</span>
    +
    +<span class="nc" id="L538">                pageInfo.set(&quot;Fonts&quot;, fontsArray);</span>
    +
    +                // Access resources dictionary
    +<span class="nc" id="L541">                ArrayNode colorSpacesArray = objectMapper.createArrayNode();</span>
    +
    +<span class="nc" id="L543">                Iterable&lt;COSName&gt; colorSpaceNames = resources.getColorSpaceNames();</span>
    +<span class="nc bnc" id="L544" title="All 2 branches missed.">                for (COSName name : colorSpaceNames) {</span>
    +<span class="nc" id="L545">                    PDColorSpace colorSpace = resources.getColorSpace(name);</span>
    +<span class="nc bnc" id="L546" title="All 2 branches missed.">                    if (colorSpace instanceof PDICCBased iccBased) {</span>
    +<span class="nc" id="L547">                        PDStream iccData = iccBased.getPDStream();</span>
    +<span class="nc" id="L548">                        byte[] iccBytes = iccData.toByteArray();</span>
    +
    +                        // TODO: Further decode and analyze the ICC data if needed
    +<span class="nc" id="L551">                        ObjectNode iccProfileNode = objectMapper.createObjectNode();</span>
    +<span class="nc" id="L552">                        iccProfileNode.put(&quot;ICC Profile Length&quot;, iccBytes.length);</span>
    +<span class="nc" id="L553">                        colorSpacesArray.add(iccProfileNode);</span>
    +                    }
    +<span class="nc" id="L555">                }</span>
    +<span class="nc" id="L556">                pageInfo.set(&quot;Color Spaces &amp; ICC Profiles&quot;, colorSpacesArray);</span>
    +
    +                // Other XObjects
    +<span class="nc" id="L559">                Map&lt;String, Integer&gt; xObjectCountMap =</span>
    +                        new HashMap&lt;&gt;(); // To store the count for each type
    +<span class="nc bnc" id="L561" title="All 2 branches missed.">                for (COSName name : resources.getXObjectNames()) {</span>
    +<span class="nc" id="L562">                    PDXObject xObject = resources.getXObject(name);</span>
    +                    String xObjectType;
    +
    +<span class="nc bnc" id="L565" title="All 2 branches missed.">                    if (xObject instanceof PDImageXObject) {</span>
    +<span class="nc" id="L566">                        xObjectType = &quot;Image&quot;;</span>
    +<span class="nc bnc" id="L567" title="All 2 branches missed.">                    } else if (xObject instanceof PDFormXObject) {</span>
    +<span class="nc" id="L568">                        xObjectType = &quot;Form&quot;;</span>
    +                    } else {
    +<span class="nc" id="L570">                        xObjectType = &quot;Other&quot;;</span>
    +                    }
    +
    +                    // Increment the count for this type in the map
    +<span class="nc" id="L574">                    xObjectCountMap.put(</span>
    +<span class="nc" id="L575">                            xObjectType, xObjectCountMap.getOrDefault(xObjectType, 0) + 1);</span>
    +<span class="nc" id="L576">                }</span>
    +
    +                // Add the count map to pageInfo (or wherever you want to store it)
    +<span class="nc" id="L579">                ObjectNode xObjectCountNode = objectMapper.createObjectNode();</span>
    +<span class="nc bnc" id="L580" title="All 2 branches missed.">                for (Map.Entry&lt;String, Integer&gt; entry : xObjectCountMap.entrySet()) {</span>
    +<span class="nc" id="L581">                    xObjectCountNode.put(entry.getKey(), entry.getValue());</span>
    +<span class="nc" id="L582">                }</span>
    +<span class="nc" id="L583">                pageInfo.set(&quot;XObjectCounts&quot;, xObjectCountNode);</span>
    +
    +<span class="nc" id="L585">                ArrayNode multimediaArray = objectMapper.createArrayNode();</span>
    +
    +<span class="nc bnc" id="L587" title="All 2 branches missed.">                for (PDAnnotation annotation : annotations) {</span>
    +<span class="nc bnc" id="L588" title="All 2 branches missed.">                    if (&quot;RichMedia&quot;.equals(annotation.getSubtype())) {</span>
    +<span class="nc" id="L589">                        ObjectNode multimediaNode = objectMapper.createObjectNode();</span>
    +                        // Extract details from the annotation as needed
    +<span class="nc" id="L591">                        multimediaArray.add(multimediaNode);</span>
    +                    }
    +<span class="nc" id="L593">                }</span>
    +
    +<span class="nc" id="L595">                pageInfo.set(&quot;Multimedia&quot;, multimediaArray);</span>
    +
    +<span class="nc" id="L597">                pageInfoParent.set(&quot;Page &quot; + (pageNum + 1), pageInfo);</span>
    +            }
    +
    +<span class="nc" id="L600">            jsonOutput.set(&quot;BasicInfo&quot;, basicInfo);</span>
    +<span class="nc" id="L601">            jsonOutput.set(&quot;DocumentInfo&quot;, docInfoNode);</span>
    +<span class="nc" id="L602">            jsonOutput.set(&quot;Compliancy&quot;, compliancy);</span>
    +<span class="nc" id="L603">            jsonOutput.set(&quot;Encryption&quot;, encryption);</span>
    +<span class="nc" id="L604">            jsonOutput.set(&quot;Permissions&quot;, permissionsNode); // set the node under &quot;Permissions&quot;</span>
    +<span class="nc" id="L605">            jsonOutput.set(&quot;Other&quot;, other);</span>
    +<span class="nc" id="L606">            jsonOutput.set(&quot;PerPageInfo&quot;, pageInfoParent);</span>
    +
    +            // Save JSON to file
    +<span class="nc" id="L609">            String jsonString =</span>
    +<span class="nc" id="L610">                    objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonOutput);</span>
    +
    +<span class="nc" id="L612">            return WebResponseUtils.bytesToWebResponse(</span>
    +<span class="nc" id="L613">                    jsonString.getBytes(StandardCharsets.UTF_8),</span>
    +                    &quot;response.json&quot;,
    +                    MediaType.APPLICATION_JSON);
    +
    +<span class="nc" id="L617">        } catch (Exception e) {</span>
    +<span class="nc" id="L618">            log.error(&quot;exception&quot;, e);</span>
    +        }
    +<span class="nc" id="L620">        return null;</span>
    +    }
    +
    +    private void setNodePermissions(PDDocument pdfBoxDoc, ObjectNode permissionsNode) {
    +<span class="nc" id="L624">        AccessPermission ap = pdfBoxDoc.getCurrentAccessPermission();</span>
    +
    +<span class="nc" id="L626">        permissionsNode.put(&quot;Document Assembly&quot;, getPermissionState(ap.canAssembleDocument()));</span>
    +<span class="nc" id="L627">        permissionsNode.put(&quot;Extracting Content&quot;, getPermissionState(ap.canExtractContent()));</span>
    +<span class="nc" id="L628">        permissionsNode.put(</span>
    +                &quot;Extracting for accessibility&quot;,
    +<span class="nc" id="L630">                getPermissionState(ap.canExtractForAccessibility()));</span>
    +<span class="nc" id="L631">        permissionsNode.put(&quot;Form Filling&quot;, getPermissionState(ap.canFillInForm()));</span>
    +<span class="nc" id="L632">        permissionsNode.put(&quot;Modifying&quot;, getPermissionState(ap.canModify()));</span>
    +<span class="nc" id="L633">        permissionsNode.put(&quot;Modifying annotations&quot;, getPermissionState(ap.canModifyAnnotations()));</span>
    +<span class="nc" id="L634">        permissionsNode.put(&quot;Printing&quot;, getPermissionState(ap.canPrint()));</span>
    +<span class="nc" id="L635">    }</span>
    +
    +    private String getPermissionState(boolean state) {
    +<span class="nc bnc" id="L638" title="All 2 branches missed.">        return state ? &quot;Allowed&quot; : &quot;Not Allowed&quot;;</span>
    +    }
    +
    +    public String getPageOrientation(double width, double height) {
    +<span class="nc bnc" id="L642" title="All 2 branches missed.">        if (width &gt; height) {</span>
    +<span class="nc" id="L643">            return &quot;Landscape&quot;;</span>
    +<span class="nc bnc" id="L644" title="All 2 branches missed.">        } else if (height &gt; width) {</span>
    +<span class="nc" id="L645">            return &quot;Portrait&quot;;</span>
    +        } else {
    +<span class="nc" id="L647">            return &quot;Square&quot;;</span>
    +        }
    +    }
    +
    +    public String getPageSize(float width, float height) {
    +        // Define standard page sizes
    +<span class="nc" id="L653">        Map&lt;String, PDRectangle&gt; standardSizes = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L654">        standardSizes.put(&quot;Letter&quot;, PDRectangle.LETTER);</span>
    +<span class="nc" id="L655">        standardSizes.put(&quot;LEGAL&quot;, PDRectangle.LEGAL);</span>
    +<span class="nc" id="L656">        standardSizes.put(&quot;A0&quot;, PDRectangle.A0);</span>
    +<span class="nc" id="L657">        standardSizes.put(&quot;A1&quot;, PDRectangle.A1);</span>
    +<span class="nc" id="L658">        standardSizes.put(&quot;A2&quot;, PDRectangle.A2);</span>
    +<span class="nc" id="L659">        standardSizes.put(&quot;A3&quot;, PDRectangle.A3);</span>
    +<span class="nc" id="L660">        standardSizes.put(&quot;A4&quot;, PDRectangle.A4);</span>
    +<span class="nc" id="L661">        standardSizes.put(&quot;A5&quot;, PDRectangle.A5);</span>
    +<span class="nc" id="L662">        standardSizes.put(&quot;A6&quot;, PDRectangle.A6);</span>
    +
    +<span class="nc bnc" id="L664" title="All 2 branches missed.">        for (Map.Entry&lt;String, PDRectangle&gt; entry : standardSizes.entrySet()) {</span>
    +<span class="nc" id="L665">            PDRectangle size = entry.getValue();</span>
    +<span class="nc bnc" id="L666" title="All 2 branches missed.">            if (isCloseToSize(width, height, size.getWidth(), size.getHeight())) {</span>
    +<span class="nc" id="L667">                return entry.getKey();</span>
    +            }
    +<span class="nc" id="L669">        }</span>
    +<span class="nc" id="L670">        return &quot;Custom&quot;;</span>
    +    }
    +
    +    private boolean isCloseToSize(
    +            float width, float height, float standardWidth, float standardHeight) {
    +<span class="nc" id="L675">        float tolerance = 1.0f; // You can adjust the tolerance as needed</span>
    +<span class="nc bnc" id="L676" title="All 2 branches missed.">        return Math.abs(width - standardWidth) &lt;= tolerance</span>
    +<span class="nc bnc" id="L677" title="All 2 branches missed.">                &amp;&amp; Math.abs(height - standardHeight) &lt;= tolerance;</span>
    +    }
    +
    +    public ObjectNode getDimensionInfo(ObjectNode dimensionInfo, float width, float height) {
    +<span class="nc" id="L681">        float ppi = 72; // Points Per Inch</span>
    +
    +<span class="nc" id="L683">        float widthInInches = width / ppi;</span>
    +<span class="nc" id="L684">        float heightInInches = height / ppi;</span>
    +
    +<span class="nc" id="L686">        float widthInCm = widthInInches * 2.54f;</span>
    +<span class="nc" id="L687">        float heightInCm = heightInInches * 2.54f;</span>
    +
    +<span class="nc" id="L689">        dimensionInfo.put(&quot;Width (px)&quot;, String.format(&quot;%.2f&quot;, width));</span>
    +<span class="nc" id="L690">        dimensionInfo.put(&quot;Height (px)&quot;, String.format(&quot;%.2f&quot;, height));</span>
    +<span class="nc" id="L691">        dimensionInfo.put(&quot;Width (in)&quot;, String.format(&quot;%.2f&quot;, widthInInches));</span>
    +<span class="nc" id="L692">        dimensionInfo.put(&quot;Height (in)&quot;, String.format(&quot;%.2f&quot;, heightInInches));</span>
    +<span class="nc" id="L693">        dimensionInfo.put(&quot;Width (cm)&quot;, String.format(&quot;%.2f&quot;, widthInCm));</span>
    +<span class="nc" id="L694">        dimensionInfo.put(&quot;Height (cm)&quot;, String.format(&quot;%.2f&quot;, heightInCm));</span>
    +<span class="nc" id="L695">        return dimensionInfo;</span>
    +    }
    +
    +    public ArrayNode exploreStructureTree(List&lt;Object&gt; nodes) {
    +<span class="nc" id="L699">        ArrayNode elementsArray = objectMapper.createArrayNode();</span>
    +<span class="nc bnc" id="L700" title="All 2 branches missed.">        if (nodes != null) {</span>
    +<span class="nc bnc" id="L701" title="All 2 branches missed.">            for (Object obj : nodes) {</span>
    +<span class="nc bnc" id="L702" title="All 2 branches missed.">                if (obj instanceof PDStructureNode node) {</span>
    +<span class="nc" id="L703">                    ObjectNode elementNode = objectMapper.createObjectNode();</span>
    +
    +<span class="nc bnc" id="L705" title="All 2 branches missed.">                    if (node instanceof PDStructureElement structureElement) {</span>
    +<span class="nc" id="L706">                        elementNode.put(&quot;Type&quot;, structureElement.getStructureType());</span>
    +<span class="nc" id="L707">                        elementNode.put(&quot;Content&quot;, getContent(structureElement));</span>
    +
    +                        // Recursively explore child elements
    +<span class="nc" id="L710">                        ArrayNode childElements = exploreStructureTree(structureElement.getKids());</span>
    +<span class="nc bnc" id="L711" title="All 2 branches missed.">                        if (childElements.size() &gt; 0) {</span>
    +<span class="nc" id="L712">                            elementNode.set(&quot;Children&quot;, childElements);</span>
    +                        }
    +                    }
    +<span class="nc" id="L715">                    elementsArray.add(elementNode);</span>
    +                }
    +<span class="nc" id="L717">            }</span>
    +        }
    +<span class="nc" id="L719">        return elementsArray;</span>
    +    }
    +
    +    public String getContent(PDStructureElement structureElement) {
    +<span class="nc" id="L723">        StringBuilder contentBuilder = new StringBuilder();</span>
    +
    +<span class="nc bnc" id="L725" title="All 2 branches missed.">        for (Object item : structureElement.getKids()) {</span>
    +<span class="nc bnc" id="L726" title="All 2 branches missed.">            if (item instanceof COSString cosString) {</span>
    +<span class="nc" id="L727">                contentBuilder.append(cosString.getString());</span>
    +<span class="nc bnc" id="L728" title="All 2 branches missed.">            } else if (item instanceof PDStructureElement) {</span>
    +                // For simplicity, we're handling only COSString and PDStructureElement here
    +                // but a more comprehensive method would handle other types too
    +<span class="nc" id="L731">                contentBuilder.append(getContent((PDStructureElement) item));</span>
    +            }
    +<span class="nc" id="L733">        }</span>
    +
    +<span class="nc" id="L735">        return contentBuilder.toString();</span>
    +    }
    +
    +    private String formatDate(Calendar calendar) {
    +<span class="nc bnc" id="L739" title="All 2 branches missed.">        if (calendar != null) {</span>
    +<span class="nc" id="L740">            SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);</span>
    +<span class="nc" id="L741">            return sdf.format(calendar.getTime());</span>
    +        } else {
    +<span class="nc" id="L743">            return null;</span>
    +        }
    +    }
    +
    +    private String getPageModeDescription(String pageMode) {
    +<span class="nc bnc" id="L748" title="All 2 branches missed.">        return pageMode != null ? pageMode.toString().replaceFirst(&quot;/&quot;, &quot;&quot;) : &quot;Unknown&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/PasswordController.html b/testResult/html/stirling.software.SPDF.controller.api.security/PasswordController.html
    new file mode 100644
    index 000000000..1a82a32a1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/PasswordController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PasswordController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">PasswordController</span></div><h1>PasswordController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">184 of 184</td><td class="ctr2">0%</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">44</td><td class="ctr2">44</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="PasswordController.java.html#L65" class="el_method">addPassword(AddPasswordRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="153" alt="153"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">13</td><td class="ctr2" id="g0">13</td><td class="ctr1" id="h0">34</td><td class="ctr2" id="i0">34</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="PasswordController.java.html#L45" class="el_method">removePassword(PDFPasswordRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="25" alt="25"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">7</td><td class="ctr2" id="i1">7</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="PasswordController.java.html#L33" class="el_method">PasswordController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/PasswordController.java.html b/testResult/html/stirling.software.SPDF.controller.api.security/PasswordController.java.html
    new file mode 100644
    index 000000000..3b4e04817
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/PasswordController.java.html
    @@ -0,0 +1,109 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PasswordController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_source">PasswordController.java</span></div><h1>PasswordController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.security;
    +
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
    +import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.security.AddPasswordRequest;
    +import stirling.software.SPDF.model.api.security.PDFPasswordRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/security&quot;)
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +public class PasswordController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L33">    public PasswordController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L34">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L35">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/remove-password&quot;)
    +    @Operation(
    +            summary = &quot;Remove password from a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint removes the password from a protected PDF file. Users need to&quot;
    +                            + &quot; provide the existing password. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; removePassword(@ModelAttribute PDFPasswordRequest request)
    +            throws IOException {
    +<span class="nc" id="L45">        MultipartFile fileInput = request.getFileInput();</span>
    +<span class="nc" id="L46">        String password = request.getPassword();</span>
    +<span class="nc" id="L47">        PDDocument document = pdfDocumentFactory.load(fileInput, password);</span>
    +<span class="nc" id="L48">        document.setAllSecurityToBeRemoved(true);</span>
    +<span class="nc" id="L49">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L51">                Filenames.toSimpleFileName(fileInput.getOriginalFilename())</span>
    +<span class="nc" id="L52">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_password_removed.pdf&quot;);
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/add-password&quot;)
    +    @Operation(
    +            summary = &quot;Add password to a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint adds password protection to a PDF file. Users can specify a set&quot;
    +                            + &quot; of permissions that should be applied to the file. Input:PDF&quot;
    +                            + &quot; Output:PDF&quot;)
    +    public ResponseEntity&lt;byte[]&gt; addPassword(@ModelAttribute AddPasswordRequest request)
    +            throws IOException {
    +<span class="nc" id="L65">        MultipartFile fileInput = request.getFileInput();</span>
    +<span class="nc" id="L66">        String ownerPassword = request.getOwnerPassword();</span>
    +<span class="nc" id="L67">        String password = request.getPassword();</span>
    +<span class="nc" id="L68">        int keyLength = request.getKeyLength();</span>
    +<span class="nc" id="L69">        boolean canAssembleDocument = request.isCanAssembleDocument();</span>
    +<span class="nc" id="L70">        boolean canExtractContent = request.isCanExtractContent();</span>
    +<span class="nc" id="L71">        boolean canExtractForAccessibility = request.isCanExtractForAccessibility();</span>
    +<span class="nc" id="L72">        boolean canFillInForm = request.isCanFillInForm();</span>
    +<span class="nc" id="L73">        boolean canModify = request.isCanModify();</span>
    +<span class="nc" id="L74">        boolean canModifyAnnotations = request.isCanModifyAnnotations();</span>
    +<span class="nc" id="L75">        boolean canPrint = request.isCanPrint();</span>
    +<span class="nc" id="L76">        boolean canPrintFaithful = request.isCanPrintFaithful();</span>
    +
    +<span class="nc" id="L78">        PDDocument document = pdfDocumentFactory.load(fileInput);</span>
    +<span class="nc" id="L79">        AccessPermission ap = new AccessPermission();</span>
    +<span class="nc bnc" id="L80" title="All 2 branches missed.">        ap.setCanAssembleDocument(!canAssembleDocument);</span>
    +<span class="nc bnc" id="L81" title="All 2 branches missed.">        ap.setCanExtractContent(!canExtractContent);</span>
    +<span class="nc bnc" id="L82" title="All 2 branches missed.">        ap.setCanExtractForAccessibility(!canExtractForAccessibility);</span>
    +<span class="nc bnc" id="L83" title="All 2 branches missed.">        ap.setCanFillInForm(!canFillInForm);</span>
    +<span class="nc bnc" id="L84" title="All 2 branches missed.">        ap.setCanModify(!canModify);</span>
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">        ap.setCanModifyAnnotations(!canModifyAnnotations);</span>
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">        ap.setCanPrint(!canPrint);</span>
    +<span class="nc bnc" id="L87" title="All 2 branches missed.">        ap.setCanPrintFaithful(!canPrintFaithful);</span>
    +<span class="nc" id="L88">        StandardProtectionPolicy spp = new StandardProtectionPolicy(ownerPassword, password, ap);</span>
    +
    +<span class="nc bnc" id="L90" title="All 4 branches missed.">        if (!&quot;&quot;.equals(ownerPassword) || !&quot;&quot;.equals(password)) {</span>
    +<span class="nc" id="L91">            spp.setEncryptionKeyLength(keyLength);</span>
    +        }
    +<span class="nc" id="L93">        spp.setPermissions(ap);</span>
    +<span class="nc" id="L94">        document.protect(spp);</span>
    +
    +<span class="nc bnc" id="L96" title="All 4 branches missed.">        if (&quot;&quot;.equals(ownerPassword) &amp;&amp; &quot;&quot;.equals(password))</span>
    +<span class="nc" id="L97">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +                    document,
    +<span class="nc" id="L99">                    Filenames.toSimpleFileName(fileInput.getOriginalFilename())</span>
    +<span class="nc" id="L100">                                    .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                            + &quot;_permissions.pdf&quot;);
    +<span class="nc" id="L102">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L104">                Filenames.toSimpleFileName(fileInput.getOriginalFilename())</span>
    +<span class="nc" id="L105">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_passworded.pdf&quot;);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/RedactController.html b/testResult/html/stirling.software.SPDF.controller.api.security/RedactController.html
    new file mode 100644
    index 000000000..5a704f8c3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/RedactController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RedactController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">RedactController</span></div><h1>RedactController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">503 of 503</td><td class="ctr2">0%</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="ctr1">28</td><td class="ctr2">28</td><td class="ctr1">126</td><td class="ctr2">126</td><td class="ctr1">11</td><td class="ctr2">11</td></tr></tfoot><tbody><tr><td id="a4"><a href="RedactController.java.html#L103" class="el_method">redactAreas(List, PDDocument, PDPageTree)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="147" alt="147"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h0">33</td><td class="ctr2" id="i0">33</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a9"><a href="RedactController.java.html#L205" class="el_method">redactPdf(RedactPdfRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="93" height="10" title="114" alt="114"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h1">31</td><td class="ctr2" id="i1">31</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a6"><a href="RedactController.java.html#L254" class="el_method">redactFoundText(PDDocument, List, float, Color)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="75" alt="75"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">16</td><td class="ctr2" id="i2">16</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a8"><a href="RedactController.java.html#L72" class="el_method">redactPDF(ManualRedactPdfRequest)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="57" alt="57"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">16</td><td class="ctr2" id="i3">16</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a7"><a href="RedactController.java.html#L158" class="el_method">redactPages(ManualRedactPdfRequest, PDDocument, PDPageTree)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="56" alt="56"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h4">12</td><td class="ctr2" id="i4">12</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="RedactController.java.html#L188" class="el_method">getPageNumbers(ManualRedactPdfRequest, int)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="21" alt="21"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a0"><a href="RedactController.java.html#L177" class="el_method">decodeOrDefault(String, Color)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="11" alt="11"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">6</td><td class="ctr2" id="i6">6</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="RedactController.java.html#L59" class="el_method">initBinder(WebDataBinder)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="8" alt="8"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a5"><a href="RedactController.java.html#L53" class="el_method">RedactController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a3"><a href="RedactController.java.html#L116" class="el_method">lambda$redactAreas$0(Integer)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a10"><a href="RedactController.java.html#L46" class="el_method">static {...}</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/RedactController.java.html b/testResult/html/stirling.software.SPDF.controller.api.security/RedactController.java.html
    new file mode 100644
    index 000000000..1feb7a71c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/RedactController.java.html
    @@ -0,0 +1,274 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RedactController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_source">RedactController.java</span></div><h1>RedactController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.security;
    +
    +import java.awt.*;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.PDPageTree;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.WebDataBinder;
    +import org.springframework.web.bind.annotation.InitBinder;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.PDFText;
    +import stirling.software.SPDF.model.api.security.ManualRedactPdfRequest;
    +import stirling.software.SPDF.model.api.security.RedactPdfRequest;
    +import stirling.software.SPDF.model.api.security.RedactionArea;
    +import stirling.software.SPDF.pdf.TextFinder;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +import stirling.software.SPDF.utils.PdfUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +import stirling.software.SPDF.utils.propertyeditor.StringToArrayListPropertyEditor;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/security&quot;)
    +<span class="nc" id="L46">@Slf4j</span>
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +public class RedactController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L53">    public RedactController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L54">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L55">    }</span>
    +
    +    @InitBinder
    +    public void initBinder(WebDataBinder binder) {
    +<span class="nc" id="L59">        binder.registerCustomEditor(</span>
    +                List.class, &quot;redactions&quot;, new StringToArrayListPropertyEditor());
    +<span class="nc" id="L61">    }</span>
    +
    +    @PostMapping(value = &quot;/redact&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Redacts areas and pages in a PDF document&quot;,
    +            description =
    +                    &quot;This operation takes an input PDF file with a list of areas, page&quot;
    +                            + &quot; number(s)/range(s)/function(s) to redact. Input:PDF, Output:PDF,&quot;
    +                            + &quot; Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; redactPDF(@ModelAttribute ManualRedactPdfRequest request)
    +            throws IOException {
    +<span class="nc" id="L72">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L73">        List&lt;RedactionArea&gt; redactionAreas = request.getRedactions();</span>
    +
    +<span class="nc" id="L75">        PDDocument document = pdfDocumentFactory.load(file);</span>
    +
    +<span class="nc" id="L77">        PDPageTree allPages = document.getDocumentCatalog().getPages();</span>
    +
    +<span class="nc" id="L79">        redactPages(request, document, allPages);</span>
    +<span class="nc" id="L80">        redactAreas(redactionAreas, document, allPages);</span>
    +
    +<span class="nc bnc" id="L82" title="All 2 branches missed.">        if (request.isConvertPDFToImage()) {</span>
    +<span class="nc" id="L83">            PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);</span>
    +<span class="nc" id="L84">            document.close();</span>
    +<span class="nc" id="L85">            document = convertedPdf;</span>
    +        }
    +
    +<span class="nc" id="L88">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L89">        document.save(baos);</span>
    +<span class="nc" id="L90">        document.close();</span>
    +
    +<span class="nc" id="L92">        byte[] pdfContent = baos.toByteArray();</span>
    +<span class="nc" id="L93">        return WebResponseUtils.bytesToWebResponse(</span>
    +                pdfContent,
    +<span class="nc" id="L95">                Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_redacted.pdf&quot;);
    +    }
    +
    +    private void redactAreas(
    +            List&lt;RedactionArea&gt; redactionAreas, PDDocument document, PDPageTree allPages)
    +            throws IOException {
    +        // Group redaction areas by page
    +<span class="nc" id="L103">        Map&lt;Integer, List&lt;RedactionArea&gt;&gt; redactionsByPage = new HashMap&lt;&gt;();</span>
    +
    +        // Process and validate each redaction area
    +<span class="nc bnc" id="L106" title="All 2 branches missed.">        for (RedactionArea redactionArea : redactionAreas) {</span>
    +<span class="nc bnc" id="L107" title="All 2 branches missed.">            if (redactionArea.getPage() == null</span>
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">                    || redactionArea.getPage() &lt;= 0</span>
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">                    || redactionArea.getHeight() == null</span>
    +<span class="nc bnc" id="L110" title="All 2 branches missed.">                    || redactionArea.getHeight() &lt;= 0.0D</span>
    +<span class="nc bnc" id="L111" title="All 2 branches missed.">                    || redactionArea.getWidth() == null</span>
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">                    || redactionArea.getWidth() &lt;= 0.0D) continue;</span>
    +
    +            // Group by page number
    +<span class="nc" id="L115">            redactionsByPage</span>
    +<span class="nc" id="L116">                    .computeIfAbsent(redactionArea.getPage(), k -&gt; new ArrayList&lt;&gt;())</span>
    +<span class="nc" id="L117">                    .add(redactionArea);</span>
    +<span class="nc" id="L118">        }</span>
    +
    +        // Process each page only once
    +<span class="nc bnc" id="L121" title="All 2 branches missed.">        for (Map.Entry&lt;Integer, List&lt;RedactionArea&gt;&gt; entry : redactionsByPage.entrySet()) {</span>
    +<span class="nc" id="L122">            Integer pageNumber = entry.getKey();</span>
    +<span class="nc" id="L123">            List&lt;RedactionArea&gt; areasForPage = entry.getValue();</span>
    +
    +<span class="nc bnc" id="L125" title="All 2 branches missed.">            if (pageNumber &gt; allPages.getCount()) {</span>
    +<span class="nc" id="L126">                continue; // Skip if page number is out of bounds</span>
    +            }
    +
    +<span class="nc" id="L129">            PDPage page = allPages.get(pageNumber - 1);</span>
    +<span class="nc" id="L130">            PDRectangle box = page.getBBox();</span>
    +
    +            // Create only one content stream per page
    +<span class="nc" id="L133">            PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(
    +                            document, page, PDPageContentStream.AppendMode.APPEND, true, true);
    +
    +            // Process all redactions for this page
    +<span class="nc bnc" id="L138" title="All 2 branches missed.">            for (RedactionArea redactionArea : areasForPage) {</span>
    +<span class="nc" id="L139">                Color redactColor = decodeOrDefault(redactionArea.getColor(), Color.BLACK);</span>
    +<span class="nc" id="L140">                contentStream.setNonStrokingColor(redactColor);</span>
    +
    +<span class="nc" id="L142">                float x = redactionArea.getX().floatValue();</span>
    +<span class="nc" id="L143">                float y = redactionArea.getY().floatValue();</span>
    +<span class="nc" id="L144">                float width = redactionArea.getWidth().floatValue();</span>
    +<span class="nc" id="L145">                float height = redactionArea.getHeight().floatValue();</span>
    +
    +<span class="nc" id="L147">                contentStream.addRect(x, box.getHeight() - y - height, width, height);</span>
    +<span class="nc" id="L148">                contentStream.fill();</span>
    +<span class="nc" id="L149">            }</span>
    +
    +<span class="nc" id="L151">            contentStream.close();</span>
    +<span class="nc" id="L152">        }</span>
    +<span class="nc" id="L153">    }</span>
    +
    +    private void redactPages(
    +            ManualRedactPdfRequest request, PDDocument document, PDPageTree allPages)
    +            throws IOException {
    +<span class="nc" id="L158">        Color redactColor = decodeOrDefault(request.getPageRedactionColor(), Color.BLACK);</span>
    +<span class="nc" id="L159">        List&lt;Integer&gt; pageNumbers = getPageNumbers(request, allPages.getCount());</span>
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">        for (Integer pageNumber : pageNumbers) {</span>
    +<span class="nc" id="L161">            PDPage page = allPages.get(pageNumber);</span>
    +
    +<span class="nc" id="L163">            PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(
    +                            document, page, PDPageContentStream.AppendMode.APPEND, true, true);
    +<span class="nc" id="L166">            contentStream.setNonStrokingColor(redactColor);</span>
    +
    +<span class="nc" id="L168">            PDRectangle box = page.getBBox();</span>
    +
    +<span class="nc" id="L170">            contentStream.addRect(0, 0, box.getWidth(), box.getHeight());</span>
    +<span class="nc" id="L171">            contentStream.fill();</span>
    +<span class="nc" id="L172">            contentStream.close();</span>
    +<span class="nc" id="L173">        }</span>
    +<span class="nc" id="L174">    }</span>
    +
    +    private Color decodeOrDefault(String hex, Color defaultColor) {
    +<span class="nc" id="L177">        Color color = null;</span>
    +        try {
    +<span class="nc" id="L179">            color = Color.decode(hex);</span>
    +<span class="nc" id="L180">        } catch (Exception e) {</span>
    +<span class="nc" id="L181">            color = defaultColor;</span>
    +<span class="nc" id="L182">        }</span>
    +
    +<span class="nc" id="L184">        return color;</span>
    +    }
    +
    +    private List&lt;Integer&gt; getPageNumbers(ManualRedactPdfRequest request, int pagesCount) {
    +<span class="nc" id="L188">        String pageNumbersInput = request.getPageNumbers();</span>
    +        String[] parsedPageNumbers =
    +<span class="nc bnc" id="L190" title="All 2 branches missed.">                pageNumbersInput != null ? pageNumbersInput.split(&quot;,&quot;) : new String[0];</span>
    +<span class="nc" id="L191">        List&lt;Integer&gt; pageNumbers =</span>
    +<span class="nc" id="L192">                GeneralUtils.parsePageList(parsedPageNumbers, pagesCount, false);</span>
    +<span class="nc" id="L193">        Collections.sort(pageNumbers);</span>
    +<span class="nc" id="L194">        return pageNumbers;</span>
    +    }
    +
    +    @PostMapping(value = &quot;/auto-redact&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Redacts listOfText in a PDF document&quot;,
    +            description =
    +                    &quot;This operation takes an input PDF file and redacts the provided listOfText.&quot;
    +                            + &quot; Input:PDF, Output:PDF, Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; redactPdf(@ModelAttribute RedactPdfRequest request)
    +            throws Exception {
    +<span class="nc" id="L205">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L206">        String listOfTextString = request.getListOfText();</span>
    +<span class="nc" id="L207">        boolean useRegex = request.isUseRegex();</span>
    +<span class="nc" id="L208">        boolean wholeWordSearchBool = request.isWholeWordSearch();</span>
    +<span class="nc" id="L209">        String colorString = request.getRedactColor();</span>
    +<span class="nc" id="L210">        float customPadding = request.getCustomPadding();</span>
    +<span class="nc" id="L211">        boolean convertPDFToImage = request.isConvertPDFToImage();</span>
    +
    +<span class="nc" id="L213">        String[] listOfText = listOfTextString.split(&quot;\n&quot;);</span>
    +<span class="nc" id="L214">        PDDocument document = pdfDocumentFactory.load(file);</span>
    +
    +        Color redactColor;
    +        try {
    +<span class="nc bnc" id="L218" title="All 2 branches missed.">            if (!colorString.startsWith(&quot;#&quot;)) {</span>
    +<span class="nc" id="L219">                colorString = &quot;#&quot; + colorString;</span>
    +            }
    +<span class="nc" id="L221">            redactColor = Color.decode(colorString);</span>
    +<span class="nc" id="L222">        } catch (NumberFormatException e) {</span>
    +<span class="nc" id="L223">            log.warn(&quot;Invalid color string provided. Using default color BLACK for redaction.&quot;);</span>
    +<span class="nc" id="L224">            redactColor = Color.BLACK;</span>
    +<span class="nc" id="L225">        }</span>
    +
    +<span class="nc bnc" id="L227" title="All 2 branches missed.">        for (String text : listOfText) {</span>
    +<span class="nc" id="L228">            text = text.trim();</span>
    +<span class="nc" id="L229">            TextFinder textFinder = new TextFinder(text, useRegex, wholeWordSearchBool);</span>
    +<span class="nc" id="L230">            List&lt;PDFText&gt; foundTexts = textFinder.getTextLocations(document);</span>
    +<span class="nc" id="L231">            redactFoundText(document, foundTexts, customPadding, redactColor);</span>
    +        }
    +
    +<span class="nc bnc" id="L234" title="All 2 branches missed.">        if (convertPDFToImage) {</span>
    +<span class="nc" id="L235">            PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);</span>
    +<span class="nc" id="L236">            document.close();</span>
    +<span class="nc" id="L237">            document = convertedPdf;</span>
    +        }
    +
    +<span class="nc" id="L240">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L241">        document.save(baos);</span>
    +<span class="nc" id="L242">        document.close();</span>
    +
    +<span class="nc" id="L244">        byte[] pdfContent = baos.toByteArray();</span>
    +<span class="nc" id="L245">        return WebResponseUtils.bytesToWebResponse(</span>
    +                pdfContent,
    +<span class="nc" id="L247">                Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_redacted.pdf&quot;);
    +    }
    +
    +    private void redactFoundText(
    +            PDDocument document, List&lt;PDFText&gt; blocks, float customPadding, Color redactColor)
    +            throws IOException {
    +<span class="nc" id="L254">        var allPages = document.getDocumentCatalog().getPages();</span>
    +
    +<span class="nc bnc" id="L256" title="All 2 branches missed.">        for (PDFText block : blocks) {</span>
    +<span class="nc" id="L257">            var page = allPages.get(block.getPageIndex());</span>
    +<span class="nc" id="L258">            PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(
    +                            document, page, PDPageContentStream.AppendMode.APPEND, true, true);
    +<span class="nc" id="L261">            contentStream.setNonStrokingColor(redactColor);</span>
    +<span class="nc" id="L262">            float padding = (block.getY2() - block.getY1()) * 0.3f + customPadding;</span>
    +<span class="nc" id="L263">            PDRectangle pageBox = page.getBBox();</span>
    +<span class="nc" id="L264">            contentStream.addRect(</span>
    +<span class="nc" id="L265">                    block.getX1(),</span>
    +<span class="nc" id="L266">                    pageBox.getHeight() - block.getY1() - padding,</span>
    +<span class="nc" id="L267">                    block.getX2() - block.getX1(),</span>
    +<span class="nc" id="L268">                    block.getY2() - block.getY1() + 2 * padding);</span>
    +<span class="nc" id="L269">            contentStream.fill();</span>
    +<span class="nc" id="L270">            contentStream.close();</span>
    +<span class="nc" id="L271">        }</span>
    +<span class="nc" id="L272">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/RemoveCertSignController.html b/testResult/html/stirling.software.SPDF.controller.api.security/RemoveCertSignController.html
    new file mode 100644
    index 000000000..7ad253d2a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/RemoveCertSignController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RemoveCertSignController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">RemoveCertSignController</span></div><h1>RemoveCertSignController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">49 of 49</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">16</td><td class="ctr2">16</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="RemoveCertSignController.java.html#L46" class="el_method">removeCertSignPDF(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="40" alt="40"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">13</td><td class="ctr2" id="i0">13</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="RemoveCertSignController.java.html#L34" class="el_method">RemoveCertSignController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="RemoveCertSignController.java.html#L60" class="el_method">lambda$removeCertSignPDF$0(PDField)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/RemoveCertSignController.java.html b/testResult/html/stirling.software.SPDF.controller.api.security/RemoveCertSignController.java.html
    new file mode 100644
    index 000000000..050259701
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/RemoveCertSignController.java.html
    @@ -0,0 +1,74 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RemoveCertSignController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_source">RemoveCertSignController.java</span></div><h1>RemoveCertSignController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.security;
    +
    +import java.util.List;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDField;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/security&quot;)
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +public class RemoveCertSignController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L34">    public RemoveCertSignController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L35">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L36">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/remove-cert-sign&quot;)
    +    @Operation(
    +            summary = &quot;Remove digital signature from PDF&quot;,
    +            description =
    +                    &quot;This endpoint accepts a PDF file and returns the PDF file without the digital&quot;
    +                            + &quot; signature. Input:PDF, Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; removeCertSignPDF(@ModelAttribute PDFFile request)
    +            throws Exception {
    +<span class="nc" id="L46">        MultipartFile pdf = request.getFileInput();</span>
    +
    +        // Load the PDF document
    +<span class="nc" id="L49">        PDDocument document = pdfDocumentFactory.load(pdf);</span>
    +
    +        // Get the document catalog
    +<span class="nc" id="L52">        PDDocumentCatalog catalog = document.getDocumentCatalog();</span>
    +
    +        // Get the AcroForm
    +<span class="nc" id="L55">        PDAcroForm acroForm = catalog.getAcroForm();</span>
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">        if (acroForm != null) {</span>
    +            // Remove signature fields safely
    +<span class="nc" id="L58">            List&lt;PDField&gt; fieldsToRemove =</span>
    +<span class="nc" id="L59">                    acroForm.getFields().stream()</span>
    +<span class="nc" id="L60">                            .filter(field -&gt; field instanceof PDSignatureField)</span>
    +<span class="nc" id="L61">                            .toList();</span>
    +
    +<span class="nc bnc" id="L63" title="All 2 branches missed.">            if (!fieldsToRemove.isEmpty()) {</span>
    +<span class="nc" id="L64">                acroForm.flatten(fieldsToRemove, false);</span>
    +            }
    +        }
    +        // Return the modified PDF as a response
    +<span class="nc" id="L68">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L70">                Filenames.toSimpleFileName(pdf.getOriginalFilename()).replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_unsigned.pdf&quot;);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/SanitizeController.html b/testResult/html/stirling.software.SPDF.controller.api.security/SanitizeController.html
    new file mode 100644
    index 000000000..92aacdc6a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/SanitizeController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SanitizeController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">SanitizeController</span></div><h1>SanitizeController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">317 of 317</td><td class="ctr2">0%</td><td class="bar">72 of 72</td><td class="ctr2">0%</td><td class="ctr1">44</td><td class="ctr2">44</td><td class="ctr1">90</td><td class="ctr2">90</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a4"><a href="SanitizeController.java.html#L93" class="el_method">sanitizeJavaScript(PDDocument)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="114" alt="114"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="26" alt="26"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">14</td><td class="ctr2" id="g0">14</td><td class="ctr1" id="h0">31</td><td class="ctr2" id="i0">31</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a6"><a href="SanitizeController.java.html#L51" class="el_method">sanitizePDF(SanitizePdfRequest)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="67" alt="67"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="55" height="10" title="12" alt="12"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">7</td><td class="ctr2" id="g2">7</td><td class="ctr1" id="h1">23</td><td class="ctr2" id="i1">23</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="SanitizeController.java.html#L171" class="el_method">sanitizeLinks(PDDocument)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="47" alt="47"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="14" alt="14"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">8</td><td class="ctr2" id="g1">8</td><td class="ctr1" id="h2">9</td><td class="ctr2" id="i2">9</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="SanitizeController.java.html#L143" class="el_method">sanitizeEmbeddedFiles(PDDocument)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="28" alt="28"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f4">4</td><td class="ctr2" id="g4">4</td><td class="ctr1" id="h3">7</td><td class="ctr2" id="i3">7</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="SanitizeController.java.html#L186" class="el_method">sanitizeFonts(PDDocument)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="28" alt="28"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="8" alt="8"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">5</td><td class="ctr2" id="g3">5</td><td class="ctr1" id="h4">7</td><td class="ctr2" id="i4">7</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a7"><a href="SanitizeController.java.html#L154" class="el_method">sanitizeXMPMetadata(PDDocument)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="14" alt="14"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h5">5</td><td class="ctr2" id="i5">5</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a1"><a href="SanitizeController.java.html#L163" class="el_method">sanitizeDocumentInfoMetadata(PDDocument)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="13" alt="13"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h6">5</td><td class="ctr2" id="i6">5</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a0"><a href="SanitizeController.java.html#L39" class="el_method">SanitizeController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="6" alt="6"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/SanitizeController.java.html b/testResult/html/stirling.software.SPDF.controller.api.security/SanitizeController.java.html
    new file mode 100644
    index 000000000..280c7ae6d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/SanitizeController.java.html
    @@ -0,0 +1,195 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SanitizeController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_source">SanitizeController.java</span></div><h1>SanitizeController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.security;
    +
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.cos.COSDictionary;
    +import org.apache.pdfbox.cos.COSName;
    +import org.apache.pdfbox.pdmodel.*;
    +import org.apache.pdfbox.pdmodel.common.PDMetadata;
    +import org.apache.pdfbox.pdmodel.interactive.action.*;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDField;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.security.SanitizePdfRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/security&quot;)
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +public class SanitizeController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L39">    public SanitizeController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L40">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/sanitize-pdf&quot;)
    +    @Operation(
    +            summary = &quot;Sanitize a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint processes a PDF file and removes specific elements based on the&quot;
    +                            + &quot; provided options. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; sanitizePDF(@ModelAttribute SanitizePdfRequest request)
    +            throws IOException {
    +<span class="nc" id="L51">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="nc" id="L52">        boolean removeJavaScript = request.isRemoveJavaScript();</span>
    +<span class="nc" id="L53">        boolean removeEmbeddedFiles = request.isRemoveEmbeddedFiles();</span>
    +<span class="nc" id="L54">        boolean removeXMPMetadata = request.isRemoveXMPMetadata();</span>
    +<span class="nc" id="L55">        boolean removeMetadata = request.isRemoveMetadata();</span>
    +<span class="nc" id="L56">        boolean removeLinks = request.isRemoveLinks();</span>
    +<span class="nc" id="L57">        boolean removeFonts = request.isRemoveFonts();</span>
    +
    +<span class="nc" id="L59">        PDDocument document = pdfDocumentFactory.load(inputFile, true);</span>
    +<span class="nc bnc" id="L60" title="All 2 branches missed.">        if (removeJavaScript) {</span>
    +<span class="nc" id="L61">            sanitizeJavaScript(document);</span>
    +        }
    +
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">        if (removeEmbeddedFiles) {</span>
    +<span class="nc" id="L65">            sanitizeEmbeddedFiles(document);</span>
    +        }
    +
    +<span class="nc bnc" id="L68" title="All 2 branches missed.">        if (removeXMPMetadata) {</span>
    +<span class="nc" id="L69">            sanitizeXMPMetadata(document);</span>
    +        }
    +
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">        if (removeMetadata) {</span>
    +<span class="nc" id="L73">            sanitizeDocumentInfoMetadata(document);</span>
    +        }
    +
    +<span class="nc bnc" id="L76" title="All 2 branches missed.">        if (removeLinks) {</span>
    +<span class="nc" id="L77">            sanitizeLinks(document);</span>
    +        }
    +
    +<span class="nc bnc" id="L80" title="All 2 branches missed.">        if (removeFonts) {</span>
    +<span class="nc" id="L81">            sanitizeFonts(document);</span>
    +        }
    +
    +<span class="nc" id="L84">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L86">                Filenames.toSimpleFileName(inputFile.getOriginalFilename())</span>
    +<span class="nc" id="L87">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_sanitized.pdf&quot;);
    +    }
    +
    +    private void sanitizeJavaScript(PDDocument document) throws IOException {
    +        // Get the root dictionary (catalog) of the PDF
    +<span class="nc" id="L93">        PDDocumentCatalog catalog = document.getDocumentCatalog();</span>
    +
    +        // Get the Names dictionary
    +<span class="nc" id="L96">        COSDictionary namesDict =</span>
    +<span class="nc" id="L97">                (COSDictionary) catalog.getCOSObject().getDictionaryObject(COSName.NAMES);</span>
    +
    +<span class="nc bnc" id="L99" title="All 2 branches missed.">        if (namesDict != null) {</span>
    +            // Get the JavaScript dictionary
    +<span class="nc" id="L101">            COSDictionary javaScriptDict =</span>
    +<span class="nc" id="L102">                    (COSDictionary) namesDict.getDictionaryObject(COSName.getPDFName(&quot;JavaScript&quot;));</span>
    +
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">            if (javaScriptDict != null) {</span>
    +                // Remove the JavaScript dictionary
    +<span class="nc" id="L106">                namesDict.removeItem(COSName.getPDFName(&quot;JavaScript&quot;));</span>
    +            }
    +        }
    +
    +<span class="nc bnc" id="L110" title="All 2 branches missed.">        for (PDPage page : document.getPages()) {</span>
    +<span class="nc bnc" id="L111" title="All 2 branches missed.">            for (PDAnnotation annotation : page.getAnnotations()) {</span>
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">                if (annotation instanceof PDAnnotationWidget widget) {</span>
    +<span class="nc" id="L113">                    PDAction action = widget.getAction();</span>
    +<span class="nc bnc" id="L114" title="All 2 branches missed.">                    if (action instanceof PDActionJavaScript) {</span>
    +<span class="nc" id="L115">                        widget.setAction(null);</span>
    +                    }
    +                }
    +<span class="nc" id="L118">            }</span>
    +<span class="nc" id="L119">            PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();</span>
    +<span class="nc bnc" id="L120" title="All 2 branches missed.">            if (acroForm != null) {</span>
    +<span class="nc bnc" id="L121" title="All 2 branches missed.">                for (PDField field : acroForm.getFields()) {</span>
    +<span class="nc" id="L122">                    PDFormFieldAdditionalActions actions = field.getActions();</span>
    +<span class="nc bnc" id="L123" title="All 2 branches missed.">                    if (actions != null) {</span>
    +<span class="nc bnc" id="L124" title="All 2 branches missed.">                        if (actions.getC() instanceof PDActionJavaScript) {</span>
    +<span class="nc" id="L125">                            actions.setC(null);</span>
    +                        }
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">                        if (actions.getF() instanceof PDActionJavaScript) {</span>
    +<span class="nc" id="L128">                            actions.setF(null);</span>
    +                        }
    +<span class="nc bnc" id="L130" title="All 2 branches missed.">                        if (actions.getK() instanceof PDActionJavaScript) {</span>
    +<span class="nc" id="L131">                            actions.setK(null);</span>
    +                        }
    +<span class="nc bnc" id="L133" title="All 2 branches missed.">                        if (actions.getV() instanceof PDActionJavaScript) {</span>
    +<span class="nc" id="L134">                            actions.setV(null);</span>
    +                        }
    +                    }
    +<span class="nc" id="L137">                }</span>
    +            }
    +<span class="nc" id="L139">        }</span>
    +<span class="nc" id="L140">    }</span>
    +
    +    private void sanitizeEmbeddedFiles(PDDocument document) {
    +<span class="nc" id="L143">        PDPageTree allPages = document.getPages();</span>
    +
    +<span class="nc bnc" id="L145" title="All 2 branches missed.">        for (PDPage page : allPages) {</span>
    +<span class="nc" id="L146">            PDResources res = page.getResources();</span>
    +<span class="nc bnc" id="L147" title="All 4 branches missed.">            if (res != null &amp;&amp; res.getCOSObject() != null) {</span>
    +<span class="nc" id="L148">                res.getCOSObject().removeItem(COSName.getPDFName(&quot;EmbeddedFiles&quot;));</span>
    +            }
    +<span class="nc" id="L150">        }</span>
    +<span class="nc" id="L151">    }</span>
    +
    +    private void sanitizeXMPMetadata(PDDocument document) {
    +<span class="nc bnc" id="L154" title="All 2 branches missed.">        if (document.getDocumentCatalog() != null) {</span>
    +<span class="nc" id="L155">            PDMetadata metadata = document.getDocumentCatalog().getMetadata();</span>
    +<span class="nc bnc" id="L156" title="All 2 branches missed.">            if (metadata != null) {</span>
    +<span class="nc" id="L157">                document.getDocumentCatalog().setMetadata(null);</span>
    +            }
    +        }
    +<span class="nc" id="L160">    }</span>
    +
    +    private void sanitizeDocumentInfoMetadata(PDDocument document) {
    +<span class="nc" id="L163">        PDDocumentInformation docInfo = document.getDocumentInformation();</span>
    +<span class="nc bnc" id="L164" title="All 2 branches missed.">        if (docInfo != null) {</span>
    +<span class="nc" id="L165">            PDDocumentInformation newInfo = new PDDocumentInformation();</span>
    +<span class="nc" id="L166">            document.setDocumentInformation(newInfo);</span>
    +        }
    +<span class="nc" id="L168">    }</span>
    +
    +    private void sanitizeLinks(PDDocument document) throws IOException {
    +<span class="nc bnc" id="L171" title="All 2 branches missed.">        for (PDPage page : document.getPages()) {</span>
    +<span class="nc bnc" id="L172" title="All 2 branches missed.">            for (PDAnnotation annotation : page.getAnnotations()) {</span>
    +<span class="nc bnc" id="L173" title="All 4 branches missed.">                if (annotation != null &amp;&amp; annotation instanceof PDAnnotationLink linkAnnotation) {</span>
    +<span class="nc" id="L174">                    PDAction action = linkAnnotation.getAction();</span>
    +<span class="nc bnc" id="L175" title="All 6 branches missed.">                    if (action != null</span>
    +                            &amp;&amp; (action instanceof PDActionLaunch
    +                                    || action instanceof PDActionURI)) {
    +<span class="nc" id="L178">                        linkAnnotation.setAction(null);</span>
    +                    }
    +                }
    +<span class="nc" id="L181">            }</span>
    +<span class="nc" id="L182">        }</span>
    +<span class="nc" id="L183">    }</span>
    +
    +    private void sanitizeFonts(PDDocument document) {
    +<span class="nc bnc" id="L186" title="All 2 branches missed.">        for (PDPage page : document.getPages()) {</span>
    +<span class="nc bnc" id="L187" title="All 2 branches missed.">            if (page != null</span>
    +<span class="nc bnc" id="L188" title="All 2 branches missed.">                    &amp;&amp; page.getResources() != null</span>
    +<span class="nc bnc" id="L189" title="All 2 branches missed.">                    &amp;&amp; page.getResources().getCOSObject() != null) {</span>
    +<span class="nc" id="L190">                page.getResources().getCOSObject().removeItem(COSName.getPDFName(&quot;Font&quot;));</span>
    +            }
    +<span class="nc" id="L192">        }</span>
    +<span class="nc" id="L193">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/ValidateSignatureController.html b/testResult/html/stirling.software.SPDF.controller.api.security/ValidateSignatureController.html
    new file mode 100644
    index 000000000..a4c6f6527
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/ValidateSignatureController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ValidateSignatureController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">ValidateSignatureController</span></div><h1>ValidateSignatureController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">345 of 345</td><td class="ctr2">0%</td><td class="bar">22 of 22</td><td class="ctr2">0%</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">78</td><td class="ctr2">78</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ValidateSignatureController.java.html#L64" class="el_method">validateSignature(SignatureValidationRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="336" alt="336"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">74</td><td class="ctr2" id="i0">74</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ValidateSignatureController.java.html#L51" class="el_method">ValidateSignatureController(CustomPDFDocumentFactory, CertificateValidationService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/ValidateSignatureController.java.html b/testResult/html/stirling.software.SPDF.controller.api.security/ValidateSignatureController.java.html
    new file mode 100644
    index 000000000..104dadd73
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/ValidateSignatureController.java.html
    @@ -0,0 +1,182 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ValidateSignatureController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_source">ValidateSignatureController.java</span></div><h1>ValidateSignatureController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.security;
    +
    +import java.io.ByteArrayInputStream;
    +import java.io.IOException;
    +import java.security.cert.CertificateException;
    +import java.security.cert.CertificateFactory;
    +import java.security.cert.X509Certificate;
    +import java.security.interfaces.RSAPublicKey;
    +import java.util.ArrayList;
    +import java.util.Date;
    +import java.util.List;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
    +import org.bouncycastle.cert.X509CertificateHolder;
    +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
    +import org.bouncycastle.cms.CMSProcessable;
    +import org.bouncycastle.cms.CMSProcessableByteArray;
    +import org.bouncycastle.cms.CMSSignedData;
    +import org.bouncycastle.cms.SignerInformation;
    +import org.bouncycastle.cms.SignerInformationStore;
    +import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
    +import org.bouncycastle.util.Store;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.security.SignatureValidationRequest;
    +import stirling.software.SPDF.model.api.security.SignatureValidationResult;
    +import stirling.software.SPDF.service.CertificateValidationService;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/security&quot;)
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +public class ValidateSignatureController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +    private final CertificateValidationService certValidationService;
    +
    +    @Autowired
    +    public ValidateSignatureController(
    +            CustomPDFDocumentFactory pdfDocumentFactory,
    +<span class="nc" id="L51">            CertificateValidationService certValidationService) {</span>
    +<span class="nc" id="L52">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L53">        this.certValidationService = certValidationService;</span>
    +<span class="nc" id="L54">    }</span>
    +
    +    @Operation(
    +            summary = &quot;Validate PDF Digital Signature&quot;,
    +            description =
    +                    &quot;Validates the digital signatures in a PDF file against default or custom&quot;
    +                            + &quot; certificates. Input:PDF Output:JSON Type:SISO&quot;)
    +    @PostMapping(value = &quot;/validate-signature&quot;)
    +    public ResponseEntity&lt;List&lt;SignatureValidationResult&gt;&gt; validateSignature(
    +            @ModelAttribute SignatureValidationRequest request) throws IOException {
    +<span class="nc" id="L64">        List&lt;SignatureValidationResult&gt; results = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L65">        MultipartFile file = request.getFileInput();</span>
    +
    +        // Load custom certificate if provided
    +<span class="nc" id="L68">        X509Certificate customCert = null;</span>
    +<span class="nc bnc" id="L69" title="All 4 branches missed.">        if (request.getCertFile() != null &amp;&amp; !request.getCertFile().isEmpty()) {</span>
    +<span class="nc" id="L70">            try (ByteArrayInputStream certStream =</span>
    +<span class="nc" id="L71">                    new ByteArrayInputStream(request.getCertFile().getBytes())) {</span>
    +<span class="nc" id="L72">                CertificateFactory cf = CertificateFactory.getInstance(&quot;X.509&quot;);</span>
    +<span class="nc" id="L73">                customCert = (X509Certificate) cf.generateCertificate(certStream);</span>
    +<span class="nc" id="L74">            } catch (CertificateException e) {</span>
    +<span class="nc" id="L75">                throw new RuntimeException(&quot;Invalid certificate file: &quot; + e.getMessage());</span>
    +<span class="nc" id="L76">            }</span>
    +        }
    +
    +<span class="nc" id="L79">        try (PDDocument document = pdfDocumentFactory.load(file.getInputStream())) {</span>
    +<span class="nc" id="L80">            List&lt;PDSignature&gt; signatures = document.getSignatureDictionaries();</span>
    +
    +<span class="nc bnc" id="L82" title="All 2 branches missed.">            for (PDSignature sig : signatures) {</span>
    +<span class="nc" id="L83">                SignatureValidationResult result = new SignatureValidationResult();</span>
    +
    +                try {
    +<span class="nc" id="L86">                    byte[] signedContent = sig.getSignedContent(file.getInputStream());</span>
    +<span class="nc" id="L87">                    byte[] signatureBytes = sig.getContents(file.getInputStream());</span>
    +
    +<span class="nc" id="L89">                    CMSProcessable content = new CMSProcessableByteArray(signedContent);</span>
    +<span class="nc" id="L90">                    CMSSignedData signedData = new CMSSignedData(content, signatureBytes);</span>
    +
    +<span class="nc" id="L92">                    Store&lt;X509CertificateHolder&gt; certStore = signedData.getCertificates();</span>
    +<span class="nc" id="L93">                    SignerInformationStore signerStore = signedData.getSignerInfos();</span>
    +
    +<span class="nc bnc" id="L95" title="All 2 branches missed.">                    for (SignerInformation signer : signerStore.getSigners()) {</span>
    +<span class="nc" id="L96">                        X509CertificateHolder certHolder =</span>
    +                                (X509CertificateHolder)
    +<span class="nc" id="L98">                                        certStore.getMatches(signer.getSID()).iterator().next();</span>
    +<span class="nc" id="L99">                        X509Certificate cert =</span>
    +<span class="nc" id="L100">                                new JcaX509CertificateConverter().getCertificate(certHolder);</span>
    +
    +<span class="nc" id="L102">                        boolean isValid =</span>
    +<span class="nc" id="L103">                                signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(cert));</span>
    +<span class="nc" id="L104">                        result.setValid(isValid);</span>
    +
    +                        // Additional validations
    +<span class="nc" id="L107">                        result.setChainValid(</span>
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">                                customCert != null</span>
    +                                        ? certValidationService
    +<span class="nc" id="L110">                                                .validateCertificateChainWithCustomCert(</span>
    +                                                        cert, customCert)
    +<span class="nc" id="L112">                                        : certValidationService.validateCertificateChain(cert));</span>
    +
    +<span class="nc" id="L114">                        result.setTrustValid(</span>
    +<span class="nc bnc" id="L115" title="All 2 branches missed.">                                customCert != null</span>
    +<span class="nc" id="L116">                                        ? certValidationService.validateTrustWithCustomCert(</span>
    +                                                cert, customCert)
    +<span class="nc" id="L118">                                        : certValidationService.validateTrustStore(cert));</span>
    +
    +<span class="nc bnc" id="L120" title="All 2 branches missed.">                        result.setNotRevoked(!certValidationService.isRevoked(cert));</span>
    +<span class="nc bnc" id="L121" title="All 2 branches missed.">                        result.setNotExpired(!cert.getNotAfter().before(new Date()));</span>
    +
    +                        // Set basic signature info
    +<span class="nc" id="L124">                        result.setSignerName(sig.getName());</span>
    +<span class="nc" id="L125">                        result.setSignatureDate(sig.getSignDate().getTime().toString());</span>
    +<span class="nc" id="L126">                        result.setReason(sig.getReason());</span>
    +<span class="nc" id="L127">                        result.setLocation(sig.getLocation());</span>
    +
    +                        // Set new certificate details
    +<span class="nc" id="L130">                        result.setIssuerDN(cert.getIssuerX500Principal().getName());</span>
    +<span class="nc" id="L131">                        result.setSubjectDN(cert.getSubjectX500Principal().getName());</span>
    +<span class="nc" id="L132">                        result.setSerialNumber(cert.getSerialNumber().toString(16)); // Hex format</span>
    +<span class="nc" id="L133">                        result.setValidFrom(cert.getNotBefore().toString());</span>
    +<span class="nc" id="L134">                        result.setValidUntil(cert.getNotAfter().toString());</span>
    +<span class="nc" id="L135">                        result.setSignatureAlgorithm(cert.getSigAlgName());</span>
    +
    +                        // Get key size (if possible)
    +                        try {
    +<span class="nc" id="L139">                            result.setKeySize(</span>
    +<span class="nc" id="L140">                                    ((RSAPublicKey) cert.getPublicKey()).getModulus().bitLength());</span>
    +<span class="nc" id="L141">                        } catch (Exception e) {</span>
    +                            // If not RSA or error, set to 0
    +<span class="nc" id="L143">                            result.setKeySize(0);</span>
    +<span class="nc" id="L144">                        }</span>
    +
    +<span class="nc" id="L146">                        result.setVersion(String.valueOf(cert.getVersion()));</span>
    +
    +                        // Set key usage
    +<span class="nc" id="L149">                        List&lt;String&gt; keyUsages = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L150">                        boolean[] keyUsageFlags = cert.getKeyUsage();</span>
    +<span class="nc bnc" id="L151" title="All 2 branches missed.">                        if (keyUsageFlags != null) {</span>
    +<span class="nc" id="L152">                            String[] keyUsageLabels = {</span>
    +                                &quot;Digital Signature&quot;, &quot;Non-Repudiation&quot;, &quot;Key Encipherment&quot;,
    +                                &quot;Data Encipherment&quot;, &quot;Key Agreement&quot;, &quot;Certificate Signing&quot;,
    +                                &quot;CRL Signing&quot;, &quot;Encipher Only&quot;, &quot;Decipher Only&quot;
    +                            };
    +<span class="nc bnc" id="L157" title="All 2 branches missed.">                            for (int i = 0; i &lt; keyUsageFlags.length; i++) {</span>
    +<span class="nc bnc" id="L158" title="All 2 branches missed.">                                if (keyUsageFlags[i]) {</span>
    +<span class="nc" id="L159">                                    keyUsages.add(keyUsageLabels[i]);</span>
    +                                }
    +                            }
    +                        }
    +<span class="nc" id="L163">                        result.setKeyUsages(keyUsages);</span>
    +
    +                        // Check if self-signed
    +<span class="nc" id="L166">                        result.setSelfSigned(</span>
    +<span class="nc" id="L167">                                cert.getSubjectX500Principal()</span>
    +<span class="nc" id="L168">                                        .equals(cert.getIssuerX500Principal()));</span>
    +<span class="nc" id="L169">                    }</span>
    +<span class="nc" id="L170">                } catch (Exception e) {</span>
    +<span class="nc" id="L171">                    result.setValid(false);</span>
    +<span class="nc" id="L172">                    result.setErrorMessage(&quot;Signature validation failed: &quot; + e.getMessage());</span>
    +<span class="nc" id="L173">                }</span>
    +
    +<span class="nc" id="L175">                results.add(result);</span>
    +<span class="nc" id="L176">            }</span>
    +        }
    +
    +<span class="nc" id="L179">        return ResponseEntity.ok(results);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/WatermarkController.html b/testResult/html/stirling.software.SPDF.controller.api.security/WatermarkController.html
    new file mode 100644
    index 000000000..1092b0278
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/WatermarkController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>WatermarkController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_class">WatermarkController</span></div><h1>WatermarkController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">505 of 505</td><td class="ctr2">0%</td><td class="bar">31 of 31</td><td class="ctr2">0%</td><td class="ctr1">21</td><td class="ctr2">21</td><td class="ctr1">113</td><td class="ctr2">113</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="WatermarkController.java.html#L146" class="el_method">addTextWatermark(PDPageContentStream, String, PDDocument, PDPage, float, int, int, float, String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="248" alt="248"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="19" alt="19"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">57</td><td class="ctr2" id="i0">57</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="WatermarkController.java.html#L255" class="el_method">addImageWatermark(PDPageContentStream, MultipartFile, PDDocument, PDPage, float, int, int, float)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="126" alt="126"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h2">22</td><td class="ctr2" id="i2">22</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="WatermarkController.java.html#L64" class="el_method">addWatermark(AddWatermarkRequest)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="125" alt="125"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h1">31</td><td class="ctr2" id="i1">31</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="WatermarkController.java.html#L51" class="el_method">WatermarkController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/WatermarkController.java.html b/testResult/html/stirling.software.SPDF.controller.api.security/WatermarkController.java.html
    new file mode 100644
    index 000000000..5e6c30f87
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/WatermarkController.java.html
    @@ -0,0 +1,301 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>WatermarkController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api.security</a> &gt; <span class="el_source">WatermarkController.java</span></div><h1>WatermarkController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api.security;
    +
    +import java.awt.*;
    +import java.awt.image.BufferedImage;
    +import java.io.File;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.nio.file.Files;
    +
    +import javax.imageio.ImageIO;
    +
    +import org.apache.commons.io.IOUtils;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.font.PDFont;
    +import org.apache.pdfbox.pdmodel.font.PDType0Font;
    +import org.apache.pdfbox.pdmodel.font.PDType1Font;
    +import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
    +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
    +import org.apache.pdfbox.util.Matrix;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.security.AddWatermarkRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.PdfUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/security&quot;)
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +public class WatermarkController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L51">    public WatermarkController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L52">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L53">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/add-watermark&quot;)
    +    @Operation(
    +            summary = &quot;Add watermark to a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint adds a watermark to a given PDF file. Users can specify the&quot;
    +                            + &quot; watermark type (text or image), rotation, opacity, width spacer, and&quot;
    +                            + &quot; height spacer. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; addWatermark(@ModelAttribute AddWatermarkRequest request)
    +            throws IOException, Exception {
    +<span class="nc" id="L64">        MultipartFile pdfFile = request.getFileInput();</span>
    +<span class="nc" id="L65">        String watermarkType = request.getWatermarkType();</span>
    +<span class="nc" id="L66">        String watermarkText = request.getWatermarkText();</span>
    +<span class="nc" id="L67">        MultipartFile watermarkImage = request.getWatermarkImage();</span>
    +<span class="nc" id="L68">        String alphabet = request.getAlphabet();</span>
    +<span class="nc" id="L69">        float fontSize = request.getFontSize();</span>
    +<span class="nc" id="L70">        float rotation = request.getRotation();</span>
    +<span class="nc" id="L71">        float opacity = request.getOpacity();</span>
    +<span class="nc" id="L72">        int widthSpacer = request.getWidthSpacer();</span>
    +<span class="nc" id="L73">        int heightSpacer = request.getHeightSpacer();</span>
    +<span class="nc" id="L74">        String customColor = request.getCustomColor();</span>
    +<span class="nc" id="L75">        boolean convertPdfToImage = request.isConvertPDFToImage();</span>
    +
    +        // Load the input PDF
    +<span class="nc" id="L78">        PDDocument document = pdfDocumentFactory.load(pdfFile);</span>
    +
    +        // Create a page in the document
    +<span class="nc bnc" id="L81" title="All 2 branches missed.">        for (PDPage page : document.getPages()) {</span>
    +
    +            // Get the page's content stream
    +<span class="nc" id="L84">            PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(
    +                            document, page, PDPageContentStream.AppendMode.APPEND, true, true);
    +
    +            // Set transparency
    +<span class="nc" id="L89">            PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();</span>
    +<span class="nc" id="L90">            graphicsState.setNonStrokingAlphaConstant(opacity);</span>
    +<span class="nc" id="L91">            contentStream.setGraphicsStateParameters(graphicsState);</span>
    +
    +<span class="nc bnc" id="L93" title="All 2 branches missed.">            if (&quot;text&quot;.equalsIgnoreCase(watermarkType)) {</span>
    +<span class="nc" id="L94">                addTextWatermark(</span>
    +                        contentStream,
    +                        watermarkText,
    +                        document,
    +                        page,
    +                        rotation,
    +                        widthSpacer,
    +                        heightSpacer,
    +                        fontSize,
    +                        alphabet,
    +                        customColor);
    +<span class="nc bnc" id="L105" title="All 2 branches missed.">            } else if (&quot;image&quot;.equalsIgnoreCase(watermarkType)) {</span>
    +<span class="nc" id="L106">                addImageWatermark(</span>
    +                        contentStream,
    +                        watermarkImage,
    +                        document,
    +                        page,
    +                        rotation,
    +                        widthSpacer,
    +                        heightSpacer,
    +                        fontSize);
    +            }
    +
    +            // Close the content stream
    +<span class="nc" id="L118">            contentStream.close();</span>
    +<span class="nc" id="L119">        }</span>
    +
    +<span class="nc bnc" id="L121" title="All 2 branches missed.">        if (convertPdfToImage) {</span>
    +<span class="nc" id="L122">            PDDocument convertedPdf = PdfUtils.convertPdfToPdfImage(document);</span>
    +<span class="nc" id="L123">            document.close();</span>
    +<span class="nc" id="L124">            document = convertedPdf;</span>
    +        }
    +
    +<span class="nc" id="L127">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L129">                Filenames.toSimpleFileName(pdfFile.getOriginalFilename())</span>
    +<span class="nc" id="L130">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_watermarked.pdf&quot;);
    +    }
    +
    +    private void addTextWatermark(
    +            PDPageContentStream contentStream,
    +            String watermarkText,
    +            PDDocument document,
    +            PDPage page,
    +            float rotation,
    +            int widthSpacer,
    +            int heightSpacer,
    +            float fontSize,
    +            String alphabet,
    +            String colorString)
    +            throws IOException {
    +<span class="nc" id="L146">        String resourceDir = &quot;&quot;;</span>
    +<span class="nc" id="L147">        PDFont font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);</span>
    +<span class="nc bnc" id="L148" title="All 5 branches missed.">        switch (alphabet) {</span>
    +            case &quot;arabic&quot;:
    +<span class="nc" id="L150">                resourceDir = &quot;static/fonts/NotoSansArabic-Regular.ttf&quot;;</span>
    +<span class="nc" id="L151">                break;</span>
    +            case &quot;japanese&quot;:
    +<span class="nc" id="L153">                resourceDir = &quot;static/fonts/Meiryo.ttf&quot;;</span>
    +<span class="nc" id="L154">                break;</span>
    +            case &quot;korean&quot;:
    +<span class="nc" id="L156">                resourceDir = &quot;static/fonts/malgun.ttf&quot;;</span>
    +<span class="nc" id="L157">                break;</span>
    +            case &quot;chinese&quot;:
    +<span class="nc" id="L159">                resourceDir = &quot;static/fonts/SimSun.ttf&quot;;</span>
    +<span class="nc" id="L160">                break;</span>
    +            case &quot;roman&quot;:
    +            default:
    +<span class="nc" id="L163">                resourceDir = &quot;static/fonts/NotoSans-Regular.ttf&quot;;</span>
    +                break;
    +        }
    +
    +<span class="nc bnc" id="L167" title="All 2 branches missed.">        if (!&quot;&quot;.equals(resourceDir)) {</span>
    +<span class="nc" id="L168">            ClassPathResource classPathResource = new ClassPathResource(resourceDir);</span>
    +<span class="nc" id="L169">            String fileExtension = resourceDir.substring(resourceDir.lastIndexOf(&quot;.&quot;));</span>
    +<span class="nc" id="L170">            File tempFile = Files.createTempFile(&quot;NotoSansFont&quot;, fileExtension).toFile();</span>
    +<span class="nc" id="L171">            try (InputStream is = classPathResource.getInputStream();</span>
    +<span class="nc" id="L172">                    FileOutputStream os = new FileOutputStream(tempFile)) {</span>
    +<span class="nc" id="L173">                IOUtils.copy(is, os);</span>
    +<span class="nc" id="L174">                font = PDType0Font.load(document, tempFile);</span>
    +            } finally {
    +<span class="nc bnc" id="L176" title="All 2 branches missed.">                if (tempFile != null) Files.deleteIfExists(tempFile.toPath());</span>
    +            }
    +        }
    +
    +<span class="nc" id="L180">        contentStream.setFont(font, fontSize);</span>
    +
    +        Color redactColor;
    +        try {
    +<span class="nc bnc" id="L184" title="All 2 branches missed.">            if (!colorString.startsWith(&quot;#&quot;)) {</span>
    +<span class="nc" id="L185">                colorString = &quot;#&quot; + colorString;</span>
    +            }
    +<span class="nc" id="L187">            redactColor = Color.decode(colorString);</span>
    +<span class="nc" id="L188">        } catch (NumberFormatException e) {</span>
    +
    +<span class="nc" id="L190">            redactColor = Color.LIGHT_GRAY;</span>
    +<span class="nc" id="L191">        }</span>
    +<span class="nc" id="L192">        contentStream.setNonStrokingColor(redactColor);</span>
    +
    +<span class="nc" id="L194">        String[] textLines = watermarkText.split(&quot;\\\\n&quot;);</span>
    +<span class="nc" id="L195">        float maxLineWidth = 0;</span>
    +
    +<span class="nc bnc" id="L197" title="All 2 branches missed.">        for (int i = 0; i &lt; textLines.length; ++i) {</span>
    +<span class="nc" id="L198">            maxLineWidth = Math.max(maxLineWidth, font.getStringWidth(textLines[i]));</span>
    +        }
    +
    +        // Set size and location of text watermark
    +<span class="nc" id="L202">        float watermarkWidth = widthSpacer + maxLineWidth * fontSize / 1000;</span>
    +<span class="nc" id="L203">        float watermarkHeight = heightSpacer + fontSize * textLines.length;</span>
    +<span class="nc" id="L204">        float pageWidth = page.getMediaBox().getWidth();</span>
    +<span class="nc" id="L205">        float pageHeight = page.getMediaBox().getHeight();</span>
    +
    +        // Calculating the new width and height depending on the angle.
    +<span class="nc" id="L208">        float radians = (float) Math.toRadians(rotation);</span>
    +<span class="nc" id="L209">        float newWatermarkWidth =</span>
    +                (float)
    +<span class="nc" id="L211">                        (Math.abs(watermarkWidth * Math.cos(radians))</span>
    +<span class="nc" id="L212">                                + Math.abs(watermarkHeight * Math.sin(radians)));</span>
    +<span class="nc" id="L213">        float newWatermarkHeight =</span>
    +                (float)
    +<span class="nc" id="L215">                        (Math.abs(watermarkWidth * Math.sin(radians))</span>
    +<span class="nc" id="L216">                                + Math.abs(watermarkHeight * Math.cos(radians)));</span>
    +
    +        // Calculating the number of rows and columns.
    +
    +<span class="nc" id="L220">        int watermarkRows = (int) (pageHeight / newWatermarkHeight + 1);</span>
    +<span class="nc" id="L221">        int watermarkCols = (int) (pageWidth / newWatermarkWidth + 1);</span>
    +
    +        // Add the text watermark
    +<span class="nc bnc" id="L224" title="All 2 branches missed.">        for (int i = 0; i &lt;= watermarkRows; i++) {</span>
    +<span class="nc bnc" id="L225" title="All 2 branches missed.">            for (int j = 0; j &lt;= watermarkCols; j++) {</span>
    +<span class="nc" id="L226">                contentStream.beginText();</span>
    +<span class="nc" id="L227">                contentStream.setTextMatrix(</span>
    +<span class="nc" id="L228">                        Matrix.getRotateInstance(</span>
    +<span class="nc" id="L229">                                (float) Math.toRadians(rotation),</span>
    +                                j * newWatermarkWidth,
    +                                i * newWatermarkHeight));
    +
    +<span class="nc bnc" id="L233" title="All 2 branches missed.">                for (int k = 0; k &lt; textLines.length; ++k) {</span>
    +<span class="nc" id="L234">                    contentStream.showText(textLines[k]);</span>
    +<span class="nc" id="L235">                    contentStream.newLineAtOffset(0, -fontSize);</span>
    +                }
    +
    +<span class="nc" id="L238">                contentStream.endText();</span>
    +            }
    +        }
    +<span class="nc" id="L241">    }</span>
    +
    +    private void addImageWatermark(
    +            PDPageContentStream contentStream,
    +            MultipartFile watermarkImage,
    +            PDDocument document,
    +            PDPage page,
    +            float rotation,
    +            int widthSpacer,
    +            int heightSpacer,
    +            float fontSize)
    +            throws IOException {
    +
    +        // Load the watermark image
    +<span class="nc" id="L255">        BufferedImage image = ImageIO.read(watermarkImage.getInputStream());</span>
    +
    +        // Compute width based on original aspect ratio
    +<span class="nc" id="L258">        float aspectRatio = (float) image.getWidth() / (float) image.getHeight();</span>
    +
    +        // Desired physical height (in PDF points)
    +<span class="nc" id="L261">        float desiredPhysicalHeight = fontSize;</span>
    +
    +        // Desired physical width based on the aspect ratio
    +<span class="nc" id="L264">        float desiredPhysicalWidth = desiredPhysicalHeight * aspectRatio;</span>
    +
    +        // Convert the BufferedImage to PDImageXObject
    +<span class="nc" id="L267">        PDImageXObject xobject = LosslessFactory.createFromImage(document, image);</span>
    +
    +        // Calculate the number of rows and columns for watermarks
    +<span class="nc" id="L270">        float pageWidth = page.getMediaBox().getWidth();</span>
    +<span class="nc" id="L271">        float pageHeight = page.getMediaBox().getHeight();</span>
    +<span class="nc" id="L272">        int watermarkRows =</span>
    +                (int) ((pageHeight + heightSpacer) / (desiredPhysicalHeight + heightSpacer));
    +<span class="nc" id="L274">        int watermarkCols =</span>
    +                (int) ((pageWidth + widthSpacer) / (desiredPhysicalWidth + widthSpacer));
    +
    +<span class="nc bnc" id="L277" title="All 2 branches missed.">        for (int i = 0; i &lt; watermarkRows; i++) {</span>
    +<span class="nc bnc" id="L278" title="All 2 branches missed.">            for (int j = 0; j &lt; watermarkCols; j++) {</span>
    +<span class="nc" id="L279">                float x = j * (desiredPhysicalWidth + widthSpacer);</span>
    +<span class="nc" id="L280">                float y = i * (desiredPhysicalHeight + heightSpacer);</span>
    +
    +                // Save the graphics state
    +<span class="nc" id="L283">                contentStream.saveGraphicsState();</span>
    +
    +                // Create rotation matrix and rotate
    +<span class="nc" id="L286">                contentStream.transform(</span>
    +<span class="nc" id="L287">                        Matrix.getTranslateInstance(</span>
    +                                x + desiredPhysicalWidth / 2, y + desiredPhysicalHeight / 2));
    +<span class="nc" id="L289">                contentStream.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));</span>
    +<span class="nc" id="L290">                contentStream.transform(</span>
    +<span class="nc" id="L291">                        Matrix.getTranslateInstance(</span>
    +                                -desiredPhysicalWidth / 2, -desiredPhysicalHeight / 2));
    +
    +                // Draw the image and restore the graphics state
    +<span class="nc" id="L295">                contentStream.drawImage(xobject, 0, 0, desiredPhysicalWidth, desiredPhysicalHeight);</span>
    +<span class="nc" id="L296">                contentStream.restoreGraphicsState();</span>
    +            }
    +        }
    +<span class="nc" id="L299">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/index.html b/testResult/html/stirling.software.SPDF.controller.api.security/index.html
    new file mode 100644
    index 000000000..c1cbbdfbb
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.security</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.security</span></div><h1>stirling.software.SPDF.controller.api.security</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4,425 of 4,425</td><td class="ctr2">0%</td><td class="bar">363 of 363</td><td class="ctr2">0%</td><td class="ctr1">238</td><td class="ctr2">238</td><td class="ctr1">1,037</td><td class="ctr2">1,037</td><td class="ctr1">54</td><td class="ctr2">54</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a2"><a href="GetInfoOnPDF.html" class="el_class">GetInfoOnPDF</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,924" alt="1,924"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="162" alt="162"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">96</td><td class="ctr2" id="g0">96</td><td class="ctr1" id="h0">418</td><td class="ctr2" id="i0">418</td><td class="ctr1" id="j0">15</td><td class="ctr2" id="k0">15</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a8"><a href="WatermarkController.html" class="el_class">WatermarkController</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="505" alt="505"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="31" alt="31"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f3">21</td><td class="ctr2" id="g3">21</td><td class="ctr1" id="h2">113</td><td class="ctr2" id="i2">113</td><td class="ctr1" id="j4">4</td><td class="ctr2" id="k4">4</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a4"><a href="RedactController.html" class="el_class">RedactController</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="503" alt="503"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="34" alt="34"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">28</td><td class="ctr2" id="g2">28</td><td class="ctr1" id="h1">126</td><td class="ctr2" id="i1">126</td><td class="ctr1" id="j1">11</td><td class="ctr2" id="k1">11</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a7"><a href="ValidateSignatureController.html" class="el_class">ValidateSignatureController</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="345" alt="345"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="22" alt="22"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f5">13</td><td class="ctr2" id="g5">13</td><td class="ctr1" id="h5">78</td><td class="ctr2" id="i5">78</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a6"><a href="SanitizeController.html" class="el_class">SanitizeController</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="317" alt="317"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="72" alt="72"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f1">44</td><td class="ctr2" id="g1">44</td><td class="ctr1" id="h3">90</td><td class="ctr2" id="i3">90</td><td class="ctr1" id="j2">8</td><td class="ctr2" id="k2">8</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a0"><a href="CertSignController.html" class="el_class">CertSignController</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="311" alt="311"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="12" alt="12"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f6">13</td><td class="ctr2" id="g6">13</td><td class="ctr1" id="h4">79</td><td class="ctr2" id="i4">79</td><td class="ctr1" id="j3">6</td><td class="ctr2" id="k3">6</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a1"><a href="CertSignController$CreateSignature.html" class="el_class">CertSignController.CreateSignature</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="287" alt="287"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f8">3</td><td class="ctr2" id="g8">3</td><td class="ctr1" id="h6">73</td><td class="ctr2" id="i6">73</td><td class="ctr1" id="j8">2</td><td class="ctr2" id="k8">2</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a3"><a href="PasswordController.html" class="el_class">PasswordController</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="184" alt="184"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="24" alt="24"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f4">15</td><td class="ctr2" id="g4">15</td><td class="ctr1" id="h7">44</td><td class="ctr2" id="i7">44</td><td class="ctr1" id="j5">3</td><td class="ctr2" id="k5">3</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a5"><a href="RemoveCertSignController.html" class="el_class">RemoveCertSignController</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="49" alt="49"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f7">5</td><td class="ctr2" id="g7">5</td><td class="ctr1" id="h8">16</td><td class="ctr2" id="i8">16</td><td class="ctr1" id="j6">3</td><td class="ctr2" id="k6">3</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api.security/index.source.html b/testResult/html/stirling.software.SPDF.controller.api.security/index.source.html
    new file mode 100644
    index 000000000..80a601105
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api.security/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api.security</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api.security</span></div><h1>stirling.software.SPDF.controller.api.security</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4,425 of 4,425</td><td class="ctr2">0%</td><td class="bar">363 of 363</td><td class="ctr2">0%</td><td class="ctr1">238</td><td class="ctr2">238</td><td class="ctr1">1,037</td><td class="ctr2">1,037</td><td class="ctr1">54</td><td class="ctr2">54</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a1"><a href="GetInfoOnPDF.java.html" class="el_source">GetInfoOnPDF.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,924" alt="1,924"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="162" alt="162"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">96</td><td class="ctr2" id="g0">96</td><td class="ctr1" id="h0">418</td><td class="ctr2" id="i0">418</td><td class="ctr1" id="j0">15</td><td class="ctr2" id="k0">15</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a0"><a href="CertSignController.java.html" class="el_source">CertSignController.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="598" alt="598"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="14" alt="14"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f4">16</td><td class="ctr2" id="g4">16</td><td class="ctr1" id="h1">152</td><td class="ctr2" id="i1">152</td><td class="ctr1" id="j2">8</td><td class="ctr2" id="k2">8</td><td class="ctr1" id="l0">2</td><td class="ctr2" id="m0">2</td></tr><tr><td id="a7"><a href="WatermarkController.java.html" class="el_source">WatermarkController.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="505" alt="505"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="31" alt="31"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">21</td><td class="ctr2" id="g3">21</td><td class="ctr1" id="h3">113</td><td class="ctr2" id="i3">113</td><td class="ctr1" id="j4">4</td><td class="ctr2" id="k4">4</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="RedactController.java.html" class="el_source">RedactController.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="503" alt="503"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="34" alt="34"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">28</td><td class="ctr2" id="g2">28</td><td class="ctr1" id="h2">126</td><td class="ctr2" id="i2">126</td><td class="ctr1" id="j1">11</td><td class="ctr2" id="k1">11</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a6"><a href="ValidateSignatureController.java.html" class="el_source">ValidateSignatureController.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="345" alt="345"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="22" alt="22"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f6">13</td><td class="ctr2" id="g6">13</td><td class="ctr1" id="h5">78</td><td class="ctr2" id="i5">78</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a5"><a href="SanitizeController.java.html" class="el_source">SanitizeController.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="317" alt="317"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="72" alt="72"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f1">44</td><td class="ctr2" id="g1">44</td><td class="ctr1" id="h4">90</td><td class="ctr2" id="i4">90</td><td class="ctr1" id="j3">8</td><td class="ctr2" id="k3">8</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a2"><a href="PasswordController.java.html" class="el_source">PasswordController.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="184" alt="184"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="24" alt="24"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">15</td><td class="ctr2" id="g5">15</td><td class="ctr1" id="h6">44</td><td class="ctr2" id="i6">44</td><td class="ctr1" id="j5">3</td><td class="ctr2" id="k5">3</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a4"><a href="RemoveCertSignController.java.html" class="el_source">RemoveCertSignController.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="49" alt="49"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">5</td><td class="ctr2" id="g7">5</td><td class="ctr1" id="h7">16</td><td class="ctr2" id="i7">16</td><td class="ctr1" id="j6">3</td><td class="ctr2" id="k6">3</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/AdditionalLanguageJsController.html b/testResult/html/stirling.software.SPDF.controller.api/AdditionalLanguageJsController.html
    new file mode 100644
    index 000000000..5d9c1b833
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/AdditionalLanguageJsController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AdditionalLanguageJsController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">AdditionalLanguageJsController</span></div><h1>AdditionalLanguageJsController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">72 of 72</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="AdditionalLanguageJsController.java.html#L60" class="el_method">toJsonArray(List)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="41" alt="41"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h1">7</td><td class="ctr2" id="i1">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="AdditionalLanguageJsController.java.html#L32" class="el_method">generateAdditionalLanguageJs(HttpServletResponse)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="73" height="10" title="25" alt="25"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="AdditionalLanguageJsController.java.html#L25" class="el_method">AdditionalLanguageJsController(LanguageService)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/AdditionalLanguageJsController.java.html b/testResult/html/stirling.software.SPDF.controller.api/AdditionalLanguageJsController.java.html
    new file mode 100644
    index 000000000..bc08520a1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/AdditionalLanguageJsController.java.html
    @@ -0,0 +1,71 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AdditionalLanguageJsController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">AdditionalLanguageJsController.java</span></div><h1>AdditionalLanguageJsController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.IOException;
    +import java.io.PrintWriter;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Set;
    +
    +import org.springframework.web.bind.annotation.GetMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import stirling.software.SPDF.service.LanguageService;
    +
    +@RestController
    +@RequestMapping(&quot;/js&quot;)
    +public class AdditionalLanguageJsController {
    +
    +    private final LanguageService languageService;
    +
    +<span class="nc" id="L25">    public AdditionalLanguageJsController(LanguageService languageService) {</span>
    +<span class="nc" id="L26">        this.languageService = languageService;</span>
    +<span class="nc" id="L27">    }</span>
    +
    +    @Hidden
    +    @GetMapping(value = &quot;/additionalLanguageCode.js&quot;, produces = &quot;application/javascript&quot;)
    +    public void generateAdditionalLanguageJs(HttpServletResponse response) throws IOException {
    +<span class="nc" id="L32">        Set&lt;String&gt; supportedLanguages = languageService.getSupportedLanguages();</span>
    +<span class="nc" id="L33">        response.setContentType(&quot;application/javascript&quot;);</span>
    +<span class="nc" id="L34">        PrintWriter writer = response.getWriter();</span>
    +        // Erstelle das JavaScript dynamisch
    +<span class="nc" id="L36">        writer.println(</span>
    +                &quot;const supportedLanguages = &quot;
    +<span class="nc" id="L38">                        + toJsonArray(new ArrayList&lt;&gt;(supportedLanguages))</span>
    +                        + &quot;;&quot;);
    +        // Generiere die `getDetailedLanguageCode`-Funktion
    +<span class="nc" id="L41">        writer.println(</span>
    +                &quot;&quot;&quot;
    +                        function getDetailedLanguageCode() {
    +                            const userLanguages = navigator.languages ? navigator.languages : [navigator.language];
    +                            for (let lang of userLanguages) {
    +                                let matchedLang = supportedLanguages.find(supportedLang =&gt; supportedLang.startsWith(lang.replace('-', '_')));
    +                                if (matchedLang) {
    +                                    return matchedLang;
    +                                }
    +                            }
    +                            // Fallback
    +                            return &quot;en_GB&quot;;
    +                        }
    +                        &quot;&quot;&quot;);
    +<span class="nc" id="L55">        writer.flush();</span>
    +<span class="nc" id="L56">    }</span>
    +
    +    // Hilfsfunktion zum Konvertieren der Liste in ein JSON-Array
    +    private String toJsonArray(List&lt;String&gt; list) {
    +<span class="nc" id="L60">        StringBuilder jsonArray = new StringBuilder(&quot;[&quot;);</span>
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        for (int i = 0; i &lt; list.size(); i++) {</span>
    +<span class="nc" id="L62">            jsonArray.append(&quot;\&quot;&quot;).append(list.get(i)).append(&quot;\&quot;&quot;);</span>
    +<span class="nc bnc" id="L63" title="All 2 branches missed.">            if (i &lt; list.size() - 1) {</span>
    +<span class="nc" id="L64">                jsonArray.append(&quot;,&quot;);</span>
    +            }
    +        }
    +<span class="nc" id="L67">        jsonArray.append(&quot;]&quot;);</span>
    +<span class="nc" id="L68">        return jsonArray.toString();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/AnalysisController.html b/testResult/html/stirling.software.SPDF.controller.api/AnalysisController.html
    new file mode 100644
    index 000000000..a94f0ae79
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/AnalysisController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AnalysisController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">AnalysisController</span></div><h1>AnalysisController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">442 of 442</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">16</td><td class="ctr2">16</td><td class="ctr1">86</td><td class="ctr2">86</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a8"><a href="AnalysisController.java.html#L175" class="el_method">getSecurityInfo(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="80" alt="80"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h0">17</td><td class="ctr2" id="i0">17</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="AnalysisController.java.html#L65" class="el_method">getDocumentProperties(PDFFile)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="67" alt="67"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h2">12</td><td class="ctr2" id="i2">12</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="AnalysisController.java.html#L128" class="el_method">getAnnotationInfo(PDFFile)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="99" height="10" title="66" alt="66"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h1">14</td><td class="ctr2" id="i1">14</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a5"><a href="AnalysisController.java.html#L106" class="el_method">getFormFields(PDFFile)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="91" height="10" title="61" alt="61"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">11</td><td class="ctr2" id="i3">11</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="AnalysisController.java.html#L153" class="el_method">getFontInfo(PDFFile)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="60" alt="60"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h4">11</td><td class="ctr2" id="i4">11</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a7"><a href="AnalysisController.java.html#L86" class="el_method">getPageDimensions(PDFFile)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="78" height="10" title="52" alt="52"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h5">10</td><td class="ctr2" id="i5">10</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="AnalysisController.java.html#L50" class="el_method">getBasicInfo(PDFFile)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="36" alt="36"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">6</td><td class="ctr2" id="i6">6</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a6"><a href="AnalysisController.java.html#L40" class="el_method">getPageCount(PDFFile)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="14" alt="14"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a0"><a href="AnalysisController.java.html#L31" class="el_method">AnalysisController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="6" alt="6"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/AnalysisController.java.html b/testResult/html/stirling.software.SPDF.controller.api/AnalysisController.java.html
    new file mode 100644
    index 000000000..8c6e56f18
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/AnalysisController.java.html
    @@ -0,0 +1,203 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AnalysisController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">AnalysisController.java</span></div><h1>AnalysisController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.IOException;
    +import java.util.*;
    +
    +import org.apache.pdfbox.cos.COSName;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDDocumentInformation;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageTree;
    +import org.apache.pdfbox.pdmodel.encryption.PDEncryption;
    +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.web.bind.annotation.*;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/analysis&quot;)
    +@Tag(name = &quot;Analysis&quot;, description = &quot;Analysis APIs&quot;)
    +public class AnalysisController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L31">    public AnalysisController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L32">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L33">    }</span>
    +
    +    @PostMapping(value = &quot;/page-count&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Get PDF page count&quot;,
    +            description = &quot;Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO&quot;)
    +    public Map&lt;String, Integer&gt; getPageCount(@ModelAttribute PDFFile file) throws IOException {
    +<span class="nc" id="L40">        try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {</span>
    +<span class="nc" id="L41">            return Map.of(&quot;pageCount&quot;, document.getNumberOfPages());</span>
    +        }
    +    }
    +
    +    @PostMapping(value = &quot;/basic-info&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Get basic PDF information&quot;,
    +            description = &quot;Returns page count, version, file size. Input:PDF Output:JSON Type:SISO&quot;)
    +    public Map&lt;String, Object&gt; getBasicInfo(@ModelAttribute PDFFile file) throws IOException {
    +<span class="nc" id="L50">        try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {</span>
    +<span class="nc" id="L51">            Map&lt;String, Object&gt; info = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L52">            info.put(&quot;pageCount&quot;, document.getNumberOfPages());</span>
    +<span class="nc" id="L53">            info.put(&quot;pdfVersion&quot;, document.getVersion());</span>
    +<span class="nc" id="L54">            info.put(&quot;fileSize&quot;, file.getFileInput().getSize());</span>
    +<span class="nc" id="L55">            return info;</span>
    +        }
    +    }
    +
    +    @PostMapping(value = &quot;/document-properties&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Get PDF document properties&quot;,
    +            description = &quot;Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO&quot;)
    +    public Map&lt;String, String&gt; getDocumentProperties(@ModelAttribute PDFFile file)
    +            throws IOException {
    +<span class="nc" id="L65">        try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {</span>
    +<span class="nc" id="L66">            PDDocumentInformation info = document.getDocumentInformation();</span>
    +<span class="nc" id="L67">            Map&lt;String, String&gt; properties = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L68">            properties.put(&quot;title&quot;, info.getTitle());</span>
    +<span class="nc" id="L69">            properties.put(&quot;author&quot;, info.getAuthor());</span>
    +<span class="nc" id="L70">            properties.put(&quot;subject&quot;, info.getSubject());</span>
    +<span class="nc" id="L71">            properties.put(&quot;keywords&quot;, info.getKeywords());</span>
    +<span class="nc" id="L72">            properties.put(&quot;creator&quot;, info.getCreator());</span>
    +<span class="nc" id="L73">            properties.put(&quot;producer&quot;, info.getProducer());</span>
    +<span class="nc" id="L74">            properties.put(&quot;creationDate&quot;, info.getCreationDate().toString());</span>
    +<span class="nc" id="L75">            properties.put(&quot;modificationDate&quot;, info.getModificationDate().toString());</span>
    +<span class="nc" id="L76">            return properties;</span>
    +        }
    +    }
    +
    +    @PostMapping(value = &quot;/page-dimensions&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Get page dimensions for all pages&quot;,
    +            description = &quot;Returns width and height of each page. Input:PDF Output:JSON Type:SISO&quot;)
    +    public List&lt;Map&lt;String, Float&gt;&gt; getPageDimensions(@ModelAttribute PDFFile file)
    +            throws IOException {
    +<span class="nc" id="L86">        try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {</span>
    +<span class="nc" id="L87">            List&lt;Map&lt;String, Float&gt;&gt; dimensions = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L88">            PDPageTree pages = document.getPages();</span>
    +
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">            for (PDPage page : pages) {</span>
    +<span class="nc" id="L91">                Map&lt;String, Float&gt; pageDim = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L92">                pageDim.put(&quot;width&quot;, page.getBBox().getWidth());</span>
    +<span class="nc" id="L93">                pageDim.put(&quot;height&quot;, page.getBBox().getHeight());</span>
    +<span class="nc" id="L94">                dimensions.add(pageDim);</span>
    +<span class="nc" id="L95">            }</span>
    +<span class="nc" id="L96">            return dimensions;</span>
    +        }
    +    }
    +
    +    @PostMapping(value = &quot;/form-fields&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Get form field information&quot;,
    +            description =
    +                    &quot;Returns count and details of form fields. Input:PDF Output:JSON Type:SISO&quot;)
    +    public Map&lt;String, Object&gt; getFormFields(@ModelAttribute PDFFile file) throws IOException {
    +<span class="nc" id="L106">        try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {</span>
    +<span class="nc" id="L107">            Map&lt;String, Object&gt; formInfo = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L108">            PDAcroForm form = document.getDocumentCatalog().getAcroForm();</span>
    +
    +<span class="nc bnc" id="L110" title="All 2 branches missed.">            if (form != null) {</span>
    +<span class="nc" id="L111">                formInfo.put(&quot;fieldCount&quot;, form.getFields().size());</span>
    +<span class="nc" id="L112">                formInfo.put(&quot;hasXFA&quot;, form.hasXFA());</span>
    +<span class="nc" id="L113">                formInfo.put(&quot;isSignaturesExist&quot;, form.isSignaturesExist());</span>
    +            } else {
    +<span class="nc" id="L115">                formInfo.put(&quot;fieldCount&quot;, 0);</span>
    +<span class="nc" id="L116">                formInfo.put(&quot;hasXFA&quot;, false);</span>
    +<span class="nc" id="L117">                formInfo.put(&quot;isSignaturesExist&quot;, false);</span>
    +            }
    +<span class="nc" id="L119">            return formInfo;</span>
    +        }
    +    }
    +
    +    @PostMapping(value = &quot;/annotation-info&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Get annotation information&quot;,
    +            description = &quot;Returns count and types of annotations. Input:PDF Output:JSON Type:SISO&quot;)
    +    public Map&lt;String, Object&gt; getAnnotationInfo(@ModelAttribute PDFFile file) throws IOException {
    +<span class="nc" id="L128">        try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {</span>
    +<span class="nc" id="L129">            Map&lt;String, Object&gt; annotInfo = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L130">            int totalAnnotations = 0;</span>
    +<span class="nc" id="L131">            Map&lt;String, Integer&gt; annotationTypes = new HashMap&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L133" title="All 2 branches missed.">            for (PDPage page : document.getPages()) {</span>
    +<span class="nc bnc" id="L134" title="All 2 branches missed.">                for (PDAnnotation annot : page.getAnnotations()) {</span>
    +<span class="nc" id="L135">                    totalAnnotations++;</span>
    +<span class="nc" id="L136">                    String subType = annot.getSubtype();</span>
    +<span class="nc" id="L137">                    annotationTypes.merge(subType, 1, Integer::sum);</span>
    +<span class="nc" id="L138">                }</span>
    +<span class="nc" id="L139">            }</span>
    +
    +<span class="nc" id="L141">            annotInfo.put(&quot;totalCount&quot;, totalAnnotations);</span>
    +<span class="nc" id="L142">            annotInfo.put(&quot;typeBreakdown&quot;, annotationTypes);</span>
    +<span class="nc" id="L143">            return annotInfo;</span>
    +        }
    +    }
    +
    +    @PostMapping(value = &quot;/font-info&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Get font information&quot;,
    +            description =
    +                    &quot;Returns list of fonts used in the document. Input:PDF Output:JSON Type:SISO&quot;)
    +    public Map&lt;String, Object&gt; getFontInfo(@ModelAttribute PDFFile file) throws IOException {
    +<span class="nc" id="L153">        try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {</span>
    +<span class="nc" id="L154">            Map&lt;String, Object&gt; fontInfo = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L155">            Set&lt;String&gt; fontNames = new HashSet&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L157" title="All 2 branches missed.">            for (PDPage page : document.getPages()) {</span>
    +<span class="nc bnc" id="L158" title="All 2 branches missed.">                for (COSName font : page.getResources().getFontNames()) {</span>
    +<span class="nc" id="L159">                    fontNames.add(font.getName());</span>
    +<span class="nc" id="L160">                }</span>
    +<span class="nc" id="L161">            }</span>
    +
    +<span class="nc" id="L163">            fontInfo.put(&quot;fontCount&quot;, fontNames.size());</span>
    +<span class="nc" id="L164">            fontInfo.put(&quot;fonts&quot;, fontNames);</span>
    +<span class="nc" id="L165">            return fontInfo;</span>
    +        }
    +    }
    +
    +    @PostMapping(value = &quot;/security-info&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Get security information&quot;,
    +            description =
    +                    &quot;Returns encryption and permission details. Input:PDF Output:JSON Type:SISO&quot;)
    +    public Map&lt;String, Object&gt; getSecurityInfo(@ModelAttribute PDFFile file) throws IOException {
    +<span class="nc" id="L175">        try (PDDocument document = pdfDocumentFactory.load(file.getFileInput())) {</span>
    +<span class="nc" id="L176">            Map&lt;String, Object&gt; securityInfo = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L177">            PDEncryption encryption = document.getEncryption();</span>
    +
    +<span class="nc bnc" id="L179" title="All 2 branches missed.">            if (encryption != null) {</span>
    +<span class="nc" id="L180">                securityInfo.put(&quot;isEncrypted&quot;, true);</span>
    +<span class="nc" id="L181">                securityInfo.put(&quot;keyLength&quot;, encryption.getLength());</span>
    +
    +                // Get permissions
    +<span class="nc" id="L184">                Map&lt;String, Boolean&gt; permissions = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L185">                permissions.put(&quot;canPrint&quot;, document.getCurrentAccessPermission().canPrint());</span>
    +<span class="nc" id="L186">                permissions.put(&quot;canModify&quot;, document.getCurrentAccessPermission().canModify());</span>
    +<span class="nc" id="L187">                permissions.put(</span>
    +                        &quot;canExtractContent&quot;,
    +<span class="nc" id="L189">                        document.getCurrentAccessPermission().canExtractContent());</span>
    +<span class="nc" id="L190">                permissions.put(</span>
    +                        &quot;canModifyAnnotations&quot;,
    +<span class="nc" id="L192">                        document.getCurrentAccessPermission().canModifyAnnotations());</span>
    +
    +<span class="nc" id="L194">                securityInfo.put(&quot;permissions&quot;, permissions);</span>
    +<span class="nc" id="L195">            } else {</span>
    +<span class="nc" id="L196">                securityInfo.put(&quot;isEncrypted&quot;, false);</span>
    +            }
    +
    +<span class="nc" id="L199">            return securityInfo;</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/CropController.html b/testResult/html/stirling.software.SPDF.controller.api/CropController.html
    new file mode 100644
    index 000000000..588c07313
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/CropController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CropController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">CropController</span></div><h1>CropController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">116 of 116</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">29</td><td class="ctr2">29</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="CropController.java.html#L46" class="el_method">cropPdf(CropPdfForm)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="110" alt="110"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">26</td><td class="ctr2" id="i0">26</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CropController.java.html#L35" class="el_method">CropController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/CropController.java.html b/testResult/html/stirling.software.SPDF.controller.api/CropController.java.html
    new file mode 100644
    index 000000000..d4e11cbdd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/CropController.java.html
    @@ -0,0 +1,97 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CropController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">CropController.java</span></div><h1>CropController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.multipdf.LayerUtility;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.general.CropPdfForm;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class CropController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L35">    public CropController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L36">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L37">    }</span>
    +
    +    @PostMapping(value = &quot;/crop&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Crops a PDF document&quot;,
    +            description =
    +                    &quot;This operation takes an input PDF file and crops it according to the given&quot;
    +                            + &quot; coordinates. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; cropPdf(@ModelAttribute CropPdfForm form) throws IOException {
    +<span class="nc" id="L46">        PDDocument sourceDocument = pdfDocumentFactory.load(form);</span>
    +
    +<span class="nc" id="L48">        PDDocument newDocument =</span>
    +<span class="nc" id="L49">                pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);</span>
    +
    +<span class="nc" id="L51">        int totalPages = sourceDocument.getNumberOfPages();</span>
    +
    +<span class="nc" id="L53">        LayerUtility layerUtility = new LayerUtility(newDocument);</span>
    +
    +<span class="nc bnc" id="L55" title="All 2 branches missed.">        for (int i = 0; i &lt; totalPages; i++) {</span>
    +<span class="nc" id="L56">            PDPage sourcePage = sourceDocument.getPage(i);</span>
    +
    +            // Create a new page with the size of the source page
    +<span class="nc" id="L59">            PDPage newPage = new PDPage(sourcePage.getMediaBox());</span>
    +<span class="nc" id="L60">            newDocument.addPage(newPage);</span>
    +<span class="nc" id="L61">            PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(newDocument, newPage, AppendMode.OVERWRITE, true, true);
    +
    +            // Import the source page as a form XObject
    +<span class="nc" id="L65">            PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);</span>
    +
    +<span class="nc" id="L67">            contentStream.saveGraphicsState();</span>
    +
    +            // Define the crop area
    +<span class="nc" id="L70">            contentStream.addRect(form.getX(), form.getY(), form.getWidth(), form.getHeight());</span>
    +<span class="nc" id="L71">            contentStream.clip();</span>
    +
    +            // Draw the entire formXObject
    +<span class="nc" id="L74">            contentStream.drawForm(formXObject);</span>
    +
    +<span class="nc" id="L76">            contentStream.restoreGraphicsState();</span>
    +
    +<span class="nc" id="L78">            contentStream.close();</span>
    +
    +            // Now, set the new page's media box to the cropped size
    +<span class="nc" id="L81">            newPage.setMediaBox(</span>
    +<span class="nc" id="L82">                    new PDRectangle(form.getX(), form.getY(), form.getWidth(), form.getHeight()));</span>
    +        }
    +
    +<span class="nc" id="L85">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L86">        newDocument.save(baos);</span>
    +<span class="nc" id="L87">        newDocument.close();</span>
    +<span class="nc" id="L88">        sourceDocument.close();</span>
    +
    +<span class="nc" id="L90">        byte[] pdfContent = baos.toByteArray();</span>
    +<span class="nc" id="L91">        return WebResponseUtils.bytesToWebResponse(</span>
    +                pdfContent,
    +<span class="nc" id="L93">                form.getFileInput().getOriginalFilename().replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_cropped.pdf&quot;);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/DatabaseController.html b/testResult/html/stirling.software.SPDF.controller.api/DatabaseController.html
    new file mode 100644
    index 000000000..a017af57f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/DatabaseController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DatabaseController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">DatabaseController</span></div><h1>DatabaseController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">229 of 229</td><td class="ctr2">0%</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="ctr1">20</td><td class="ctr2">20</td><td class="ctr1">62</td><td class="ctr2">62</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a4"><a href="DatabaseController.java.html#L55" class="el_method">importDatabase(MultipartFile, RedirectAttributes)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="68" alt="68"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h0">16</td><td class="ctr2" id="i0">16</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="DatabaseController.java.html#L136" class="el_method">downloadFile(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="98" height="10" title="56" alt="56"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h1">14</td><td class="ctr2" id="i1">14</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="DatabaseController.java.html#L84" class="el_method">importDatabaseFromBackupUI(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="40" alt="40"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h2">13</td><td class="ctr2" id="i2">13</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="DatabaseController.java.html#L111" class="el_method">deleteFile(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="39" alt="39"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h3">11</td><td class="ctr2" id="i3">11</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="DatabaseController.java.html#L162" class="el_method">createDatabaseBackup()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="11" alt="11"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="DatabaseController.java.html#L41" class="el_method">DatabaseController(DatabaseService)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="6" alt="6"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="DatabaseController.java.html#L90" class="el_method">lambda$importDatabaseFromBackupUI$0(String, FileInfo)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="5" alt="5"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a7"><a href="DatabaseController.java.html#L31" class="el_method">static {...}</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="4" alt="4"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/DatabaseController.java.html b/testResult/html/stirling.software.SPDF.controller.api/DatabaseController.java.html
    new file mode 100644
    index 000000000..f7ce0269e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/DatabaseController.java.html
    @@ -0,0 +1,168 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DatabaseController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">DatabaseController.java</span></div><h1>DatabaseController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.URI;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.StandardCopyOption;
    +
    +import org.eclipse.jetty.http.HttpStatus;
    +import org.springframework.context.annotation.Conditional;
    +import org.springframework.core.io.InputStreamResource;
    +import org.springframework.http.HttpHeaders;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.security.access.prepost.PreAuthorize;
    +import org.springframework.stereotype.Controller;
    +import org.springframework.web.bind.annotation.*;
    +import org.springframework.web.multipart.MultipartFile;
    +import org.springframework.web.servlet.mvc.support.RedirectAttributes;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.Parameter;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.database.DatabaseService;
    +
    +<span class="nc" id="L31">@Slf4j</span>
    +@Controller
    +@RequestMapping(&quot;/api/v1/database&quot;)
    +@PreAuthorize(&quot;hasRole('ROLE_ADMIN')&quot;)
    +@Conditional(H2SQLCondition.class)
    +@Tag(name = &quot;Database&quot;, description = &quot;Database APIs for backup, import, and management&quot;)
    +public class DatabaseController {
    +
    +    private final DatabaseService databaseService;
    +
    +<span class="nc" id="L41">    public DatabaseController(DatabaseService databaseService) {</span>
    +<span class="nc" id="L42">        this.databaseService = databaseService;</span>
    +<span class="nc" id="L43">    }</span>
    +
    +    @Operation(
    +            summary = &quot;Import a database backup file&quot;,
    +            description = &quot;Uploads and imports a database backup SQL file.&quot;)
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;import-database&quot;)
    +    public String importDatabase(
    +            @Parameter(description = &quot;SQL file to import&quot;, required = true)
    +                    @RequestParam(&quot;fileInput&quot;)
    +                    MultipartFile file,
    +            RedirectAttributes redirectAttributes)
    +            throws IOException {
    +<span class="nc bnc" id="L55" title="All 4 branches missed.">        if (file == null || file.isEmpty()) {</span>
    +<span class="nc" id="L56">            redirectAttributes.addAttribute(&quot;error&quot;, &quot;fileNullOrEmpty&quot;);</span>
    +<span class="nc" id="L57">            return &quot;redirect:/database&quot;;</span>
    +        }
    +<span class="nc" id="L59">        log.info(&quot;Received file: {}&quot;, file.getOriginalFilename());</span>
    +<span class="nc" id="L60">        Path tempTemplatePath = Files.createTempFile(&quot;backup_&quot;, &quot;.sql&quot;);</span>
    +<span class="nc" id="L61">        try (InputStream in = file.getInputStream()) {</span>
    +<span class="nc" id="L62">            Files.copy(in, tempTemplatePath, StandardCopyOption.REPLACE_EXISTING);</span>
    +<span class="nc" id="L63">            boolean importSuccess = databaseService.importDatabaseFromUI(tempTemplatePath);</span>
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">            if (importSuccess) {</span>
    +<span class="nc" id="L65">                redirectAttributes.addAttribute(&quot;infoMessage&quot;, &quot;importIntoDatabaseSuccessed&quot;);</span>
    +            } else {
    +<span class="nc" id="L67">                redirectAttributes.addAttribute(&quot;error&quot;, &quot;failedImportFile&quot;);</span>
    +            }
    +<span class="nc" id="L69">        } catch (Exception e) {</span>
    +<span class="nc" id="L70">            log.error(&quot;Error importing database: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L71">            redirectAttributes.addAttribute(&quot;error&quot;, &quot;failedImportFile&quot;);</span>
    +<span class="nc" id="L72">        }</span>
    +<span class="nc" id="L73">        return &quot;redirect:/database&quot;;</span>
    +    }
    +
    +    @Hidden
    +    @Operation(
    +            summary = &quot;Import database backup by filename&quot;,
    +            description = &quot;Imports a database backup file from the server using its file name.&quot;)
    +    @GetMapping(&quot;/import-database-file/{fileName}&quot;)
    +    public String importDatabaseFromBackupUI(
    +            @Parameter(description = &quot;Name of the file to import&quot;, required = true) @PathVariable
    +                    String fileName) {
    +<span class="nc bnc" id="L84" title="All 4 branches missed.">        if (fileName == null || fileName.isEmpty()) {</span>
    +<span class="nc" id="L85">            return &quot;redirect:/database?error=fileNullOrEmpty&quot;;</span>
    +        }
    +        // Check if the file exists in the backup list
    +<span class="nc" id="L88">        boolean fileExists =</span>
    +<span class="nc" id="L89">                databaseService.getBackupList().stream()</span>
    +<span class="nc" id="L90">                        .anyMatch(backup -&gt; backup.getFileName().equals(fileName));</span>
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">        if (!fileExists) {</span>
    +<span class="nc" id="L92">            log.error(&quot;File {} not found in backup list&quot;, fileName);</span>
    +<span class="nc" id="L93">            return &quot;redirect:/database?error=fileNotFound&quot;;</span>
    +        }
    +<span class="nc" id="L95">        log.info(&quot;Received file: {}&quot;, fileName);</span>
    +<span class="nc bnc" id="L96" title="All 2 branches missed.">        if (databaseService.importDatabaseFromUI(fileName)) {</span>
    +<span class="nc" id="L97">            log.info(&quot;File {} imported to database&quot;, fileName);</span>
    +<span class="nc" id="L98">            return &quot;redirect:/database?infoMessage=importIntoDatabaseSuccessed&quot;;</span>
    +        }
    +<span class="nc" id="L100">        return &quot;redirect:/database?error=failedImportFile&quot;;</span>
    +    }
    +
    +    @Hidden
    +    @Operation(
    +            summary = &quot;Delete a database backup file&quot;,
    +            description = &quot;Deletes a specified database backup file from the server.&quot;)
    +    @GetMapping(&quot;/delete/{fileName}&quot;)
    +    public String deleteFile(
    +            @Parameter(description = &quot;Name of the file to delete&quot;, required = true) @PathVariable
    +                    String fileName) {
    +<span class="nc bnc" id="L111" title="All 4 branches missed.">        if (fileName == null || fileName.isEmpty()) {</span>
    +<span class="nc" id="L112">            throw new IllegalArgumentException(&quot;File must not be null or empty&quot;);</span>
    +        }
    +        try {
    +<span class="nc bnc" id="L115" title="All 2 branches missed.">            if (databaseService.deleteBackupFile(fileName)) {</span>
    +<span class="nc" id="L116">                log.info(&quot;Deleted file: {}&quot;, fileName);</span>
    +            } else {
    +<span class="nc" id="L118">                log.error(&quot;Failed to delete file: {}&quot;, fileName);</span>
    +<span class="nc" id="L119">                return &quot;redirect:/database?error=failedToDeleteFile&quot;;</span>
    +            }
    +<span class="nc" id="L121">        } catch (IOException e) {</span>
    +<span class="nc" id="L122">            log.error(&quot;Error deleting file: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L123">            return &quot;redirect:/database?error=&quot; + e.getMessage();</span>
    +<span class="nc" id="L124">        }</span>
    +<span class="nc" id="L125">        return &quot;redirect:/database&quot;;</span>
    +    }
    +
    +    @Hidden
    +    @Operation(
    +            summary = &quot;Download a database backup file&quot;,
    +            description = &quot;Downloads the specified database backup file from the server.&quot;)
    +    @GetMapping(&quot;/download/{fileName}&quot;)
    +    public ResponseEntity&lt;?&gt; downloadFile(
    +            @Parameter(description = &quot;Name of the file to download&quot;, required = true) @PathVariable
    +                    String fileName) {
    +<span class="nc bnc" id="L136" title="All 4 branches missed.">        if (fileName == null || fileName.isEmpty()) {</span>
    +<span class="nc" id="L137">            throw new IllegalArgumentException(&quot;File must not be null or empty&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L140">            Path filePath = databaseService.getBackupFilePath(fileName);</span>
    +<span class="nc" id="L141">            InputStreamResource resource = new InputStreamResource(Files.newInputStream(filePath));</span>
    +<span class="nc" id="L142">            return ResponseEntity.ok()</span>
    +<span class="nc" id="L143">                    .header(HttpHeaders.CONTENT_DISPOSITION, &quot;attachment;filename=&quot; + fileName)</span>
    +<span class="nc" id="L144">                    .contentType(MediaType.APPLICATION_OCTET_STREAM)</span>
    +<span class="nc" id="L145">                    .contentLength(Files.size(filePath))</span>
    +<span class="nc" id="L146">                    .body(resource);</span>
    +<span class="nc" id="L147">        } catch (IOException e) {</span>
    +<span class="nc" id="L148">            log.error(&quot;Error downloading file: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L149">            return ResponseEntity.status(HttpStatus.SEE_OTHER_303)</span>
    +<span class="nc" id="L150">                    .location(URI.create(&quot;/database?error=downloadFailed&quot;))</span>
    +<span class="nc" id="L151">                    .build();</span>
    +        }
    +    }
    +
    +    @Operation(
    +            summary = &quot;Create a database backup&quot;,
    +            description =
    +                    &quot;This endpoint triggers the creation of a database backup and redirects to the&quot;
    +                            + &quot; database management page.&quot;)
    +    @GetMapping(&quot;/createDatabaseBackup&quot;)
    +    public String createDatabaseBackup() {
    +<span class="nc" id="L162">        log.info(&quot;Starting database backup creation...&quot;);</span>
    +<span class="nc" id="L163">        databaseService.exportDatabase();</span>
    +<span class="nc" id="L164">        log.info(&quot;Database backup successfully created.&quot;);</span>
    +<span class="nc" id="L165">        return &quot;redirect:/database?infoMessage=backupCreated&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/H2SQLCondition.html b/testResult/html/stirling.software.SPDF.controller.api/H2SQLCondition.html
    new file mode 100644
    index 000000000..f2028454b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/H2SQLCondition.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>H2SQLCondition</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">H2SQLCondition</span></div><h1>H2SQLCondition</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">26 of 26</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="H2SQLCondition.java.html#L11" class="el_method">matches(ConditionContext, AnnotatedTypeMetadata)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="23" alt="23"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="H2SQLCondition.java.html#L7" class="el_method">H2SQLCondition()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/H2SQLCondition.java.html b/testResult/html/stirling.software.SPDF.controller.api/H2SQLCondition.java.html
    new file mode 100644
    index 000000000..6521cc6ae
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/H2SQLCondition.java.html
    @@ -0,0 +1,20 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>H2SQLCondition.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">H2SQLCondition.java</span></div><h1>H2SQLCondition.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import org.springframework.context.annotation.Condition;
    +import org.springframework.context.annotation.ConditionContext;
    +import org.springframework.core.type.AnnotatedTypeMetadata;
    +
    +<span class="nc" id="L7">public class H2SQLCondition implements Condition {</span>
    +
    +    @Override
    +    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    +<span class="nc" id="L11">        boolean enableCustomDatabase =</span>
    +<span class="nc" id="L12">                Boolean.parseBoolean(</span>
    +<span class="nc" id="L13">                        context.getEnvironment()</span>
    +<span class="nc" id="L14">                                .getProperty(&quot;system.datasource.enableCustomDatabase&quot;));</span>
    +<span class="nc" id="L15">        String dataSourceType = context.getEnvironment().getProperty(&quot;system.datasource.type&quot;);</span>
    +<span class="nc bnc" id="L16" title="All 4 branches missed.">        return !enableCustomDatabase</span>
    +<span class="nc bnc" id="L17" title="All 2 branches missed.">                || (enableCustomDatabase &amp;&amp; &quot;h2&quot;.equalsIgnoreCase(dataSourceType));</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/MergeController.html b/testResult/html/stirling.software.SPDF.controller.api/MergeController.html
    new file mode 100644
    index 000000000..199f069fa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/MergeController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MergeController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">MergeController</span></div><h1>MergeController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">311 of 311</td><td class="ctr2">0%</td><td class="bar">25 of 25</td><td class="ctr2">0%</td><td class="ctr1">24</td><td class="ctr2">24</td><td class="ctr1">89</td><td class="ctr2">89</td><td class="ctr1">10</td><td class="ctr2">10</td></tr></tfoot><tbody><tr><td id="a8"><a href="MergeController.java.html#L126" class="el_method">mergePdfs(MergePdfsRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="159" alt="159"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">47</td><td class="ctr2" id="i0">47</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a7"><a href="MergeController.java.html#L54" class="el_method">mergeDocuments(List)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="32" alt="32"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h3">7</td><td class="ctr2" id="i3">7</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="MergeController.java.html#L87" class="el_method">lambda$getSortComparator$1(MultipartFile, MultipartFile)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="29" alt="29"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h1">9</td><td class="ctr2" id="i1">9</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="MergeController.java.html#L71" class="el_method">lambda$getSortComparator$0(MultipartFile, MultipartFile)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="29" alt="29"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h2">9</td><td class="ctr2" id="i2">9</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="MergeController.java.html#L102" class="el_method">lambda$getSortComparator$2(MultipartFile, MultipartFile)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="27" alt="27"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h4">7</td><td class="ctr2" id="i4">7</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="MergeController.java.html#L65" class="el_method">getSortComparator(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="20" alt="20"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="5" alt="5"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="MergeController.java.html#L48" class="el_method">MergeController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a9"><a href="MergeController.java.html#L40" class="el_method">static {...}</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a5"><a href="MergeController.java.html#L166" class="el_method">lambda$mergePdfs$4(PDField)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="3" alt="3"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a4"><a href="MergeController.java.html#L113" class="el_method">lambda$getSortComparator$3(MultipartFile, MultipartFile)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/MergeController.java.html b/testResult/html/stirling.software.SPDF.controller.api/MergeController.java.html
    new file mode 100644
    index 000000000..f94d5b2e2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/MergeController.java.html
    @@ -0,0 +1,205 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MergeController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">MergeController.java</span></div><h1>MergeController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Paths;
    +import java.nio.file.attribute.BasicFileAttributes;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Comparator;
    +import java.util.List;
    +
    +import org.apache.pdfbox.multipdf.PDFMergerUtility;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDField;
    +import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.general.MergePdfsRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +<span class="nc" id="L40">@Slf4j</span>
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class MergeController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L48">    public MergeController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L49">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L50">    }</span>
    +
    +    // Merges a list of PDDocument objects into a single PDDocument
    +    public PDDocument mergeDocuments(List&lt;PDDocument&gt; documents) throws IOException {
    +<span class="nc" id="L54">        PDDocument mergedDoc = pdfDocumentFactory.createNewDocument();</span>
    +<span class="nc bnc" id="L55" title="All 2 branches missed.">        for (PDDocument doc : documents) {</span>
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">            for (PDPage page : doc.getPages()) {</span>
    +<span class="nc" id="L57">                mergedDoc.addPage(page);</span>
    +<span class="nc" id="L58">            }</span>
    +<span class="nc" id="L59">        }</span>
    +<span class="nc" id="L60">        return mergedDoc;</span>
    +    }
    +
    +    // Returns a comparator for sorting MultipartFile arrays based on the given sort type
    +    private Comparator&lt;MultipartFile&gt; getSortComparator(String sortType) {
    +<span class="nc bnc" id="L65" title="All 5 branches missed.">        switch (sortType) {</span>
    +            case &quot;byFileName&quot;:
    +<span class="nc" id="L67">                return Comparator.comparing(MultipartFile::getOriginalFilename);</span>
    +            case &quot;byDateModified&quot;:
    +<span class="nc" id="L69">                return (file1, file2) -&gt; {</span>
    +                    try {
    +<span class="nc" id="L71">                        BasicFileAttributes attr1 =</span>
    +<span class="nc" id="L72">                                Files.readAttributes(</span>
    +<span class="nc" id="L73">                                        Paths.get(file1.getOriginalFilename()),</span>
    +                                        BasicFileAttributes.class);
    +<span class="nc" id="L75">                        BasicFileAttributes attr2 =</span>
    +<span class="nc" id="L76">                                Files.readAttributes(</span>
    +<span class="nc" id="L77">                                        Paths.get(file2.getOriginalFilename()),</span>
    +                                        BasicFileAttributes.class);
    +<span class="nc" id="L79">                        return attr1.lastModifiedTime().compareTo(attr2.lastModifiedTime());</span>
    +<span class="nc" id="L80">                    } catch (IOException e) {</span>
    +<span class="nc" id="L81">                        return 0; // If there's an error, treat them as equal</span>
    +                    }
    +                };
    +            case &quot;byDateCreated&quot;:
    +<span class="nc" id="L85">                return (file1, file2) -&gt; {</span>
    +                    try {
    +<span class="nc" id="L87">                        BasicFileAttributes attr1 =</span>
    +<span class="nc" id="L88">                                Files.readAttributes(</span>
    +<span class="nc" id="L89">                                        Paths.get(file1.getOriginalFilename()),</span>
    +                                        BasicFileAttributes.class);
    +<span class="nc" id="L91">                        BasicFileAttributes attr2 =</span>
    +<span class="nc" id="L92">                                Files.readAttributes(</span>
    +<span class="nc" id="L93">                                        Paths.get(file2.getOriginalFilename()),</span>
    +                                        BasicFileAttributes.class);
    +<span class="nc" id="L95">                        return attr1.creationTime().compareTo(attr2.creationTime());</span>
    +<span class="nc" id="L96">                    } catch (IOException e) {</span>
    +<span class="nc" id="L97">                        return 0; // If there's an error, treat them as equal</span>
    +                    }
    +                };
    +            case &quot;byPDFTitle&quot;:
    +<span class="nc" id="L101">                return (file1, file2) -&gt; {</span>
    +<span class="nc" id="L102">                    try (PDDocument doc1 = pdfDocumentFactory.load(file1);</span>
    +<span class="nc" id="L103">                            PDDocument doc2 = pdfDocumentFactory.load(file2)) {</span>
    +<span class="nc" id="L104">                        String title1 = doc1.getDocumentInformation().getTitle();</span>
    +<span class="nc" id="L105">                        String title2 = doc2.getDocumentInformation().getTitle();</span>
    +<span class="nc" id="L106">                        return title1.compareTo(title2);</span>
    +<span class="nc" id="L107">                    } catch (IOException e) {</span>
    +<span class="nc" id="L108">                        return 0;</span>
    +                    }
    +                };
    +            case &quot;orderProvided&quot;:
    +            default:
    +<span class="nc" id="L113">                return (file1, file2) -&gt; 0; // Default is the order provided</span>
    +        }
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/merge-pdfs&quot;)
    +    @Operation(
    +            summary = &quot;Merge multiple PDF files into one&quot;,
    +            description =
    +                    &quot;This endpoint merges multiple PDF files into a single PDF file. The merged&quot;
    +                            + &quot; file will contain all pages from the input files in the order they were&quot;
    +                            + &quot; provided. Input:PDF Output:PDF Type:MISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; mergePdfs(@ModelAttribute MergePdfsRequest form)
    +            throws IOException {
    +<span class="nc" id="L126">        List&lt;File&gt; filesToDelete = new ArrayList&lt;&gt;(); // List of temporary files to delete</span>
    +<span class="nc" id="L127">        File mergedTempFile = null;</span>
    +<span class="nc" id="L128">        PDDocument mergedDocument = null;</span>
    +
    +<span class="nc" id="L130">        boolean removeCertSign = form.isRemoveCertSign();</span>
    +
    +        try {
    +<span class="nc" id="L133">            MultipartFile[] files = form.getFileInput();</span>
    +<span class="nc" id="L134">            Arrays.sort(</span>
    +                    files,
    +<span class="nc" id="L136">                    getSortComparator(</span>
    +<span class="nc" id="L137">                            form.getSortType())); // Sort files based on the given sort type</span>
    +
    +<span class="nc" id="L139">            PDFMergerUtility mergerUtility = new PDFMergerUtility();</span>
    +<span class="nc" id="L140">            long totalSize = 0;</span>
    +<span class="nc bnc" id="L141" title="All 2 branches missed.">            for (MultipartFile multipartFile : files) {</span>
    +<span class="nc" id="L142">                totalSize += multipartFile.getSize();</span>
    +<span class="nc" id="L143">                File tempFile =</span>
    +<span class="nc" id="L144">                        GeneralUtils.convertMultipartFileToFile(</span>
    +                                multipartFile); // Convert MultipartFile to File
    +<span class="nc" id="L146">                filesToDelete.add(tempFile); // Add temp file to the list for later deletion</span>
    +<span class="nc" id="L147">                mergerUtility.addSource(tempFile); // Add source file to the merger utility</span>
    +            }
    +
    +<span class="nc" id="L150">            mergedTempFile = Files.createTempFile(&quot;merged-&quot;, &quot;.pdf&quot;).toFile();</span>
    +<span class="nc" id="L151">            mergerUtility.setDestinationFileName(mergedTempFile.getAbsolutePath());</span>
    +
    +<span class="nc" id="L153">            mergerUtility.mergeDocuments(</span>
    +<span class="nc" id="L154">                    pdfDocumentFactory.getStreamCacheFunction(totalSize)); // Merge the documents</span>
    +
    +            // Load the merged PDF document
    +<span class="nc" id="L157">            mergedDocument = pdfDocumentFactory.load(mergedTempFile);</span>
    +
    +            // Remove signatures if removeCertSign is true
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">            if (removeCertSign) {</span>
    +<span class="nc" id="L161">                PDDocumentCatalog catalog = mergedDocument.getDocumentCatalog();</span>
    +<span class="nc" id="L162">                PDAcroForm acroForm = catalog.getAcroForm();</span>
    +<span class="nc bnc" id="L163" title="All 2 branches missed.">                if (acroForm != null) {</span>
    +<span class="nc" id="L164">                    List&lt;PDField&gt; fieldsToRemove =</span>
    +<span class="nc" id="L165">                            acroForm.getFields().stream()</span>
    +<span class="nc" id="L166">                                    .filter(field -&gt; field instanceof PDSignatureField)</span>
    +<span class="nc" id="L167">                                    .toList();</span>
    +
    +<span class="nc bnc" id="L169" title="All 2 branches missed.">                    if (!fieldsToRemove.isEmpty()) {</span>
    +<span class="nc" id="L170">                        acroForm.flatten(</span>
    +                                fieldsToRemove,
    +                                false); // Flatten the fields, effectively removing them
    +                    }
    +                }
    +            }
    +
    +            // Save the modified document to a new ByteArrayOutputStream
    +<span class="nc" id="L178">            ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L179">            mergedDocument.save(baos);</span>
    +
    +<span class="nc" id="L181">            String mergedFileName =</span>
    +<span class="nc" id="L182">                    files[0].getOriginalFilename().replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                            + &quot;_merged_unsigned.pdf&quot;;
    +<span class="nc" id="L184">            return WebResponseUtils.boasToWebResponse(</span>
    +                    baos, mergedFileName); // Return the modified PDF
    +
    +<span class="nc" id="L187">        } catch (Exception ex) {</span>
    +<span class="nc" id="L188">            log.error(&quot;Error in merge pdf process&quot;, ex);</span>
    +<span class="nc" id="L189">            throw ex;</span>
    +        } finally {
    +<span class="nc bnc" id="L191" title="All 2 branches missed.">            if (mergedDocument != null) {</span>
    +<span class="nc" id="L192">                mergedDocument.close(); // Close the merged document</span>
    +            }
    +<span class="nc bnc" id="L194" title="All 2 branches missed.">            for (File file : filesToDelete) {</span>
    +<span class="nc bnc" id="L195" title="All 2 branches missed.">                if (file != null) {</span>
    +<span class="nc" id="L196">                    Files.deleteIfExists(file.toPath()); // Delete temporary files</span>
    +                }
    +<span class="nc" id="L198">            }</span>
    +<span class="nc bnc" id="L199" title="All 2 branches missed.">            if (mergedTempFile != null) {</span>
    +<span class="nc" id="L200">                Files.deleteIfExists(mergedTempFile.toPath());</span>
    +            }
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/MultiPageLayoutController.html b/testResult/html/stirling.software.SPDF.controller.api/MultiPageLayoutController.html
    new file mode 100644
    index 000000000..8577599a8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/MultiPageLayoutController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MultiPageLayoutController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">MultiPageLayoutController</span></div><h1>MultiPageLayoutController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">295 of 295</td><td class="ctr2">0%</td><td class="bar">22 of 22</td><td class="ctr2">0%</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">63</td><td class="ctr2">63</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="MultiPageLayoutController.java.html#L51" class="el_method">mergeMultiplePagesIntoOne(MergeMultiplePagesRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="289" alt="289"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">60</td><td class="ctr2" id="i0">60</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="MultiPageLayoutController.java.html#L38" class="el_method">MultiPageLayoutController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/MultiPageLayoutController.java.html b/testResult/html/stirling.software.SPDF.controller.api/MultiPageLayoutController.java.html
    new file mode 100644
    index 000000000..ac35c195b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/MultiPageLayoutController.java.html
    @@ -0,0 +1,150 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MultiPageLayoutController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">MultiPageLayoutController.java</span></div><h1>MultiPageLayoutController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.awt.*;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.multipdf.LayerUtility;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.apache.pdfbox.util.Matrix;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.general.MergeMultiplePagesRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class MultiPageLayoutController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L38">    public MultiPageLayoutController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L39">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L40">    }</span>
    +
    +    @PostMapping(value = &quot;/multi-page-layout&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Merge multiple pages of a PDF document into a single page&quot;,
    +            description =
    +                    &quot;This operation takes an input PDF file and the number of pages to merge into a&quot;
    +                            + &quot; single sheet in the output PDF file. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; mergeMultiplePagesIntoOne(
    +            @ModelAttribute MergeMultiplePagesRequest request) throws IOException {
    +
    +<span class="nc" id="L51">        int pagesPerSheet = request.getPagesPerSheet();</span>
    +<span class="nc" id="L52">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L53">        boolean addBorder = request.isAddBorder();</span>
    +
    +<span class="nc bnc" id="L55" title="All 4 branches missed.">        if (pagesPerSheet != 2</span>
    +                &amp;&amp; pagesPerSheet != 3
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">                &amp;&amp; pagesPerSheet != (int) Math.sqrt(pagesPerSheet) * Math.sqrt(pagesPerSheet)) {</span>
    +<span class="nc" id="L58">            throw new IllegalArgumentException(&quot;pagesPerSheet must be 2, 3 or a perfect square&quot;);</span>
    +        }
    +
    +        int cols =
    +<span class="nc bnc" id="L62" title="All 4 branches missed.">                pagesPerSheet == 2 || pagesPerSheet == 3</span>
    +<span class="nc" id="L63">                        ? pagesPerSheet</span>
    +<span class="nc" id="L64">                        : (int) Math.sqrt(pagesPerSheet);</span>
    +<span class="nc bnc" id="L65" title="All 4 branches missed.">        int rows = pagesPerSheet == 2 || pagesPerSheet == 3 ? 1 : (int) Math.sqrt(pagesPerSheet);</span>
    +
    +<span class="nc" id="L67">        PDDocument sourceDocument = pdfDocumentFactory.load(file);</span>
    +<span class="nc" id="L68">        PDDocument newDocument =</span>
    +<span class="nc" id="L69">                pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);</span>
    +<span class="nc" id="L70">        PDPage newPage = new PDPage(PDRectangle.A4);</span>
    +<span class="nc" id="L71">        newDocument.addPage(newPage);</span>
    +
    +<span class="nc" id="L73">        int totalPages = sourceDocument.getNumberOfPages();</span>
    +<span class="nc" id="L74">        float cellWidth = newPage.getMediaBox().getWidth() / cols;</span>
    +<span class="nc" id="L75">        float cellHeight = newPage.getMediaBox().getHeight() / rows;</span>
    +
    +<span class="nc" id="L77">        PDPageContentStream contentStream =</span>
    +                new PDPageContentStream(
    +                        newDocument, newPage, PDPageContentStream.AppendMode.APPEND, true, true);
    +<span class="nc" id="L80">        LayerUtility layerUtility = new LayerUtility(newDocument);</span>
    +
    +<span class="nc" id="L82">        float borderThickness = 1.5f; // Specify border thickness as required</span>
    +<span class="nc" id="L83">        contentStream.setLineWidth(borderThickness);</span>
    +<span class="nc" id="L84">        contentStream.setStrokingColor(Color.BLACK);</span>
    +
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">        for (int i = 0; i &lt; totalPages; i++) {</span>
    +<span class="nc bnc" id="L87" title="All 4 branches missed.">            if (i != 0 &amp;&amp; i % pagesPerSheet == 0) {</span>
    +                // Close the current content stream and create a new page and content stream
    +<span class="nc" id="L89">                contentStream.close();</span>
    +<span class="nc" id="L90">                newPage = new PDPage(PDRectangle.A4);</span>
    +<span class="nc" id="L91">                newDocument.addPage(newPage);</span>
    +<span class="nc" id="L92">                contentStream =</span>
    +                        new PDPageContentStream(
    +                                newDocument,
    +                                newPage,
    +                                PDPageContentStream.AppendMode.APPEND,
    +                                true,
    +                                true);
    +            }
    +
    +<span class="nc" id="L101">            PDPage sourcePage = sourceDocument.getPage(i);</span>
    +<span class="nc" id="L102">            PDRectangle rect = sourcePage.getMediaBox();</span>
    +<span class="nc" id="L103">            float scaleWidth = cellWidth / rect.getWidth();</span>
    +<span class="nc" id="L104">            float scaleHeight = cellHeight / rect.getHeight();</span>
    +<span class="nc" id="L105">            float scale = Math.min(scaleWidth, scaleHeight);</span>
    +
    +<span class="nc" id="L107">            int adjustedPageIndex =</span>
    +                    i % pagesPerSheet; // This will reset the index for every new page
    +<span class="nc" id="L109">            int rowIndex = adjustedPageIndex / cols;</span>
    +<span class="nc" id="L110">            int colIndex = adjustedPageIndex % cols;</span>
    +
    +<span class="nc" id="L112">            float x = colIndex * cellWidth + (cellWidth - rect.getWidth() * scale) / 2;</span>
    +<span class="nc" id="L113">            float y =</span>
    +<span class="nc" id="L114">                    newPage.getMediaBox().getHeight()</span>
    +                            - ((rowIndex + 1) * cellHeight
    +<span class="nc" id="L116">                                    - (cellHeight - rect.getHeight() * scale) / 2);</span>
    +
    +<span class="nc" id="L118">            contentStream.saveGraphicsState();</span>
    +<span class="nc" id="L119">            contentStream.transform(Matrix.getTranslateInstance(x, y));</span>
    +<span class="nc" id="L120">            contentStream.transform(Matrix.getScaleInstance(scale, scale));</span>
    +
    +<span class="nc" id="L122">            PDFormXObject formXObject = layerUtility.importPageAsForm(sourceDocument, i);</span>
    +<span class="nc" id="L123">            contentStream.drawForm(formXObject);</span>
    +
    +<span class="nc" id="L125">            contentStream.restoreGraphicsState();</span>
    +
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">            if (addBorder) {</span>
    +                // Draw border around each page
    +<span class="nc" id="L129">                float borderX = colIndex * cellWidth;</span>
    +<span class="nc" id="L130">                float borderY = newPage.getMediaBox().getHeight() - (rowIndex + 1) * cellHeight;</span>
    +<span class="nc" id="L131">                contentStream.addRect(borderX, borderY, cellWidth, cellHeight);</span>
    +<span class="nc" id="L132">                contentStream.stroke();</span>
    +            }
    +        }
    +
    +<span class="nc" id="L136">        contentStream.close(); // Close the final content stream</span>
    +<span class="nc" id="L137">        sourceDocument.close();</span>
    +
    +<span class="nc" id="L139">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L140">        newDocument.save(baos);</span>
    +<span class="nc" id="L141">        newDocument.close();</span>
    +
    +<span class="nc" id="L143">        byte[] result = baos.toByteArray();</span>
    +<span class="nc" id="L144">        return WebResponseUtils.bytesToWebResponse(</span>
    +                result,
    +<span class="nc" id="L146">                Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_layoutChanged.pdf&quot;);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/PdfImageRemovalController.html b/testResult/html/stirling.software.SPDF.controller.api/PdfImageRemovalController.html
    new file mode 100644
    index 000000000..21ac3cd58
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/PdfImageRemovalController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfImageRemovalController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">PdfImageRemovalController</span></div><h1>PdfImageRemovalController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">41 of 41</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="PdfImageRemovalController.java.html#L68" class="el_method">removeImages(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="32" alt="32"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PdfImageRemovalController.java.html#L44" class="el_method">PdfImageRemovalController(PdfImageRemovalService, CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/PdfImageRemovalController.java.html b/testResult/html/stirling.software.SPDF.controller.api/PdfImageRemovalController.java.html
    new file mode 100644
    index 000000000..489c09a2e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/PdfImageRemovalController.java.html
    @@ -0,0 +1,89 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfImageRemovalController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">PdfImageRemovalController.java</span></div><h1>PdfImageRemovalController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.service.PdfImageRemovalService;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +/**
    + * Controller class for handling PDF image removal requests. Provides an endpoint to remove images
    + * from a PDF file to reduce its size.
    + */
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class PdfImageRemovalController {
    +
    +    // Service for removing images from PDFs
    +    private final PdfImageRemovalService pdfImageRemovalService;
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    /**
    +     * Constructor for dependency injection of PdfImageRemovalService.
    +     *
    +     * @param pdfImageRemovalService The service used for removing images from PDFs.
    +     */
    +    @Autowired
    +    public PdfImageRemovalController(
    +            PdfImageRemovalService pdfImageRemovalService,
    +<span class="nc" id="L44">            CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L45">        this.pdfImageRemovalService = pdfImageRemovalService;</span>
    +<span class="nc" id="L46">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L47">    }</span>
    +
    +    /**
    +     * Endpoint to remove images from a PDF file.
    +     *
    +     * &lt;p&gt;This method processes the uploaded PDF file, removes all images, and returns the modified
    +     * PDF file with a new name indicating that images were removed.
    +     *
    +     * @param file The PDF file with images to be removed.
    +     * @return ResponseEntity containing the modified PDF file as byte array with appropriate
    +     *     content type and filename.
    +     * @throws IOException If an error occurs while processing the PDF file.
    +     */
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/remove-image-pdf&quot;)
    +    @Operation(
    +            summary = &quot;Remove images from file to reduce the file size.&quot;,
    +            description =
    +                    &quot;This endpoint remove images from file to reduce the file size.Input:PDF&quot;
    +                            + &quot; Output:PDF Type:MISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; removeImages(@ModelAttribute PDFFile file) throws IOException {
    +        // Load the PDF document
    +<span class="nc" id="L68">        PDDocument document = pdfDocumentFactory.load(file);</span>
    +
    +        // Remove images from the PDF document using the service
    +<span class="nc" id="L71">        PDDocument modifiedDocument = pdfImageRemovalService.removeImagesFromPdf(document);</span>
    +
    +        // Create a ByteArrayOutputStream to hold the modified PDF data
    +<span class="nc" id="L74">        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();</span>
    +
    +        // Save the modified PDF document to the output stream
    +<span class="nc" id="L77">        modifiedDocument.save(outputStream);</span>
    +<span class="nc" id="L78">        modifiedDocument.close();</span>
    +
    +        // Generate a new filename for the modified PDF
    +<span class="nc" id="L81">        String mergedFileName =</span>
    +<span class="nc" id="L82">                file.getFileInput().getOriginalFilename().replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_removed_images.pdf&quot;;
    +
    +        // Convert the byte array to a web response and return it
    +<span class="nc" id="L86">        return WebResponseUtils.bytesToWebResponse(outputStream.toByteArray(), mergedFileName);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/PdfOverlayController.html b/testResult/html/stirling.software.SPDF.controller.api/PdfOverlayController.html
    new file mode 100644
    index 000000000..cdd85c5ac
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/PdfOverlayController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfOverlayController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">PdfOverlayController</span></div><h1>PdfOverlayController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">353 of 353</td><td class="ctr2">0%</td><td class="bar">36 of 36</td><td class="ctr2">0%</td><td class="ctr1">26</td><td class="ctr2">26</td><td class="ctr1">84</td><td class="ctr2">84</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a3"><a href="PdfOverlayController.java.html#L53" class="el_method">overlayPdfs(OverlayPdfsRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="134" alt="134"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h0">32</td><td class="ctr2" id="i0">32</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a6"><a href="PdfOverlayController.java.html#L137" class="el_method">sequentialOverlay(Map, File[], int, List)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="68" alt="68"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h1">17</td><td class="ctr2" id="i1">17</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="PdfOverlayController.java.html#L188" class="el_method">fixedRepeatOverlay(Map, File[], int[], int)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="61" alt="61"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="10" alt="10"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h2">13</td><td class="ctr2" id="i2">13</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a5"><a href="PdfOverlayController.java.html#L114" class="el_method">prepareOverlayGuide(int, File[], String, int[], List)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="39" alt="39"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">4</td><td class="ctr2" id="g3">4</td><td class="ctr1" id="h3">10</td><td class="ctr2" id="i3">10</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="PdfOverlayController.java.html#L172" class="el_method">interleavedOverlay(Map, File[], int)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="37" alt="37"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h4">7</td><td class="ctr2" id="i4">7</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="PdfOverlayController.java.html#L164" class="el_method">getNumberOfPages(File)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="8" alt="8"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h6">2</td><td class="ctr2" id="i6">2</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="PdfOverlayController.java.html#L41" class="el_method">PdfOverlayController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/PdfOverlayController.java.html b/testResult/html/stirling.software.SPDF.controller.api/PdfOverlayController.java.html
    new file mode 100644
    index 000000000..f26cdcf3a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/PdfOverlayController.java.html
    @@ -0,0 +1,213 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfOverlayController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">PdfOverlayController.java</span></div><h1>PdfOverlayController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.util.ArrayList;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.apache.pdfbox.Loader;
    +import org.apache.pdfbox.multipdf.Overlay;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.general.OverlayPdfsRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class PdfOverlayController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L41">    public PdfOverlayController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L42">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L43">    }</span>
    +
    +    @PostMapping(value = &quot;/overlay-pdfs&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Overlay PDF files in various modes&quot;,
    +            description =
    +                    &quot;Overlay PDF files onto a base PDF with different modes: Sequential,&quot;
    +                            + &quot; Interleaved, or Fixed Repeat. Input:PDF Output:PDF Type:MIMO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; overlayPdfs(@ModelAttribute OverlayPdfsRequest request)
    +            throws IOException {
    +<span class="nc" id="L53">        MultipartFile baseFile = request.getFileInput();</span>
    +<span class="nc" id="L54">        int overlayPos = request.getOverlayPosition();</span>
    +
    +<span class="nc" id="L56">        MultipartFile[] overlayFiles = request.getOverlayFiles();</span>
    +<span class="nc" id="L57">        File[] overlayPdfFiles = new File[overlayFiles.length];</span>
    +<span class="nc" id="L58">        List&lt;File&gt; tempFiles = new ArrayList&lt;&gt;(); // List to keep track of temporary files</span>
    +
    +        try {
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">            for (int i = 0; i &lt; overlayFiles.length; i++) {</span>
    +<span class="nc" id="L62">                overlayPdfFiles[i] = GeneralUtils.multipartToFile(overlayFiles[i]);</span>
    +            }
    +
    +<span class="nc" id="L65">            String mode = request.getOverlayMode(); // &quot;SequentialOverlay&quot;, &quot;InterleavedOverlay&quot;,</span>
    +            // &quot;FixedRepeatOverlay&quot;
    +<span class="nc" id="L67">            int[] counts = request.getCounts(); // Used for FixedRepeatOverlay mode</span>
    +
    +<span class="nc" id="L69">            try (PDDocument basePdf = pdfDocumentFactory.load(baseFile);</span>
    +<span class="nc" id="L70">                    Overlay overlay = new Overlay()) {</span>
    +<span class="nc" id="L71">                Map&lt;Integer, String&gt; overlayGuide =</span>
    +<span class="nc" id="L72">                        prepareOverlayGuide(</span>
    +<span class="nc" id="L73">                                basePdf.getNumberOfPages(),</span>
    +                                overlayPdfFiles,
    +                                mode,
    +                                counts,
    +                                tempFiles);
    +
    +<span class="nc" id="L79">                overlay.setInputPDF(basePdf);</span>
    +<span class="nc bnc" id="L80" title="All 2 branches missed.">                if (overlayPos == 0) {</span>
    +<span class="nc" id="L81">                    overlay.setOverlayPosition(Overlay.Position.FOREGROUND);</span>
    +                } else {
    +<span class="nc" id="L83">                    overlay.setOverlayPosition(Overlay.Position.BACKGROUND);</span>
    +                }
    +
    +<span class="nc" id="L86">                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L87">                overlay.overlay(overlayGuide).save(outputStream);</span>
    +<span class="nc" id="L88">                byte[] data = outputStream.toByteArray();</span>
    +<span class="nc" id="L89">                String outputFilename =</span>
    +<span class="nc" id="L90">                        Filenames.toSimpleFileName(baseFile.getOriginalFilename())</span>
    +<span class="nc" id="L91">                                        .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                                + &quot;_overlayed.pdf&quot;; // Remove file extension and append .pdf
    +
    +<span class="nc" id="L94">                return WebResponseUtils.bytesToWebResponse(</span>
    +                        data, outputFilename, MediaType.APPLICATION_PDF);
    +            }
    +        } finally {
    +<span class="nc bnc" id="L98" title="All 2 branches missed.">            for (File overlayPdfFile : overlayPdfFiles) {</span>
    +<span class="nc bnc" id="L99" title="All 2 branches missed.">                if (overlayPdfFile != null) {</span>
    +<span class="nc" id="L100">                    Files.deleteIfExists(overlayPdfFile.toPath());</span>
    +                }
    +            }
    +<span class="nc bnc" id="L103" title="All 2 branches missed.">            for (File tempFile : tempFiles) { // Delete temporary files</span>
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">                if (tempFile != null) {</span>
    +<span class="nc" id="L105">                    Files.deleteIfExists(tempFile.toPath());</span>
    +                }
    +<span class="nc" id="L107">            }</span>
    +        }
    +    }
    +
    +    private Map&lt;Integer, String&gt; prepareOverlayGuide(
    +            int basePageCount, File[] overlayFiles, String mode, int[] counts, List&lt;File&gt; tempFiles)
    +            throws IOException {
    +<span class="nc" id="L114">        Map&lt;Integer, String&gt; overlayGuide = new HashMap&lt;&gt;();</span>
    +<span class="nc bnc" id="L115" title="All 4 branches missed.">        switch (mode) {</span>
    +            case &quot;SequentialOverlay&quot;:
    +<span class="nc" id="L117">                sequentialOverlay(overlayGuide, overlayFiles, basePageCount, tempFiles);</span>
    +<span class="nc" id="L118">                break;</span>
    +            case &quot;InterleavedOverlay&quot;:
    +<span class="nc" id="L120">                interleavedOverlay(overlayGuide, overlayFiles, basePageCount);</span>
    +<span class="nc" id="L121">                break;</span>
    +            case &quot;FixedRepeatOverlay&quot;:
    +<span class="nc" id="L123">                fixedRepeatOverlay(overlayGuide, overlayFiles, counts, basePageCount);</span>
    +<span class="nc" id="L124">                break;</span>
    +            default:
    +<span class="nc" id="L126">                throw new IllegalArgumentException(&quot;Invalid overlay mode&quot;);</span>
    +        }
    +<span class="nc" id="L128">        return overlayGuide;</span>
    +    }
    +
    +    private void sequentialOverlay(
    +            Map&lt;Integer, String&gt; overlayGuide,
    +            File[] overlayFiles,
    +            int basePageCount,
    +            List&lt;File&gt; tempFiles)
    +            throws IOException {
    +<span class="nc" id="L137">        int overlayFileIndex = 0;</span>
    +<span class="nc" id="L138">        int pageCountInCurrentOverlay = 0;</span>
    +
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">        for (int basePageIndex = 1; basePageIndex &lt;= basePageCount; basePageIndex++) {</span>
    +<span class="nc bnc" id="L141" title="All 2 branches missed.">            if (pageCountInCurrentOverlay == 0</span>
    +                    || pageCountInCurrentOverlay
    +<span class="nc bnc" id="L143" title="All 2 branches missed.">                            &gt;= getNumberOfPages(overlayFiles[overlayFileIndex])) {</span>
    +<span class="nc" id="L144">                pageCountInCurrentOverlay = 0;</span>
    +<span class="nc" id="L145">                overlayFileIndex = (overlayFileIndex + 1) % overlayFiles.length;</span>
    +            }
    +
    +<span class="nc" id="L148">            try (PDDocument overlayPdf = Loader.loadPDF(overlayFiles[overlayFileIndex])) {</span>
    +<span class="nc" id="L149">                PDDocument singlePageDocument = new PDDocument();</span>
    +<span class="nc" id="L150">                singlePageDocument.addPage(overlayPdf.getPage(pageCountInCurrentOverlay));</span>
    +<span class="nc" id="L151">                File tempFile = Files.createTempFile(&quot;overlay-page-&quot;, &quot;.pdf&quot;).toFile();</span>
    +<span class="nc" id="L152">                singlePageDocument.save(tempFile);</span>
    +<span class="nc" id="L153">                singlePageDocument.close();</span>
    +
    +<span class="nc" id="L155">                overlayGuide.put(basePageIndex, tempFile.getAbsolutePath());</span>
    +<span class="nc" id="L156">                tempFiles.add(tempFile); // Keep track of the temporary file for cleanup</span>
    +            }
    +
    +<span class="nc" id="L159">            pageCountInCurrentOverlay++;</span>
    +        }
    +<span class="nc" id="L161">    }</span>
    +
    +    private int getNumberOfPages(File file) throws IOException {
    +<span class="nc" id="L164">        try (PDDocument doc = Loader.loadPDF(file)) {</span>
    +<span class="nc" id="L165">            return doc.getNumberOfPages();</span>
    +        }
    +    }
    +
    +    private void interleavedOverlay(
    +            Map&lt;Integer, String&gt; overlayGuide, File[] overlayFiles, int basePageCount)
    +            throws IOException {
    +<span class="nc bnc" id="L172" title="All 2 branches missed.">        for (int basePageIndex = 1; basePageIndex &lt;= basePageCount; basePageIndex++) {</span>
    +<span class="nc" id="L173">            File overlayFile = overlayFiles[(basePageIndex - 1) % overlayFiles.length];</span>
    +
    +            // Load the overlay document to check its page count
    +<span class="nc" id="L176">            try (PDDocument overlayPdf = Loader.loadPDF(overlayFile)) {</span>
    +<span class="nc" id="L177">                int overlayPageCount = overlayPdf.getNumberOfPages();</span>
    +<span class="nc bnc" id="L178" title="All 2 branches missed.">                if ((basePageIndex - 1) % overlayPageCount &lt; overlayPageCount) {</span>
    +<span class="nc" id="L179">                    overlayGuide.put(basePageIndex, overlayFile.getAbsolutePath());</span>
    +                }
    +            }
    +        }
    +<span class="nc" id="L183">    }</span>
    +
    +    private void fixedRepeatOverlay(
    +            Map&lt;Integer, String&gt; overlayGuide, File[] overlayFiles, int[] counts, int basePageCount)
    +            throws IOException {
    +<span class="nc bnc" id="L188" title="All 2 branches missed.">        if (overlayFiles.length != counts.length) {</span>
    +<span class="nc" id="L189">            throw new IllegalArgumentException(</span>
    +                    &quot;Counts array length must match the number of overlay files&quot;);
    +        }
    +<span class="nc" id="L192">        int currentPage = 1;</span>
    +<span class="nc bnc" id="L193" title="All 2 branches missed.">        for (int i = 0; i &lt; overlayFiles.length; i++) {</span>
    +<span class="nc" id="L194">            File overlayFile = overlayFiles[i];</span>
    +<span class="nc" id="L195">            int repeatCount = counts[i];</span>
    +
    +            // Load the overlay document to check its page count
    +<span class="nc" id="L198">            try (PDDocument overlayPdf = Loader.loadPDF(overlayFile)) {</span>
    +<span class="nc" id="L199">                int overlayPageCount = overlayPdf.getNumberOfPages();</span>
    +<span class="nc bnc" id="L200" title="All 2 branches missed.">                for (int j = 0; j &lt; repeatCount; j++) {</span>
    +<span class="nc bnc" id="L201" title="All 2 branches missed.">                    for (int page = 0; page &lt; overlayPageCount; page++) {</span>
    +<span class="nc bnc" id="L202" title="All 2 branches missed.">                        if (currentPage &gt; basePageCount) break;</span>
    +<span class="nc" id="L203">                        overlayGuide.put(currentPage++, overlayFile.getAbsolutePath());</span>
    +                    }
    +                }
    +            }
    +        }
    +<span class="nc" id="L208">    }</span>
    +}
    +
    +// Additional classes like OverlayPdfsRequest, WebResponseUtils, etc. are assumed to be defined
    +// elsewhere.
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/RearrangePagesPDFController.html b/testResult/html/stirling.software.SPDF.controller.api/RearrangePagesPDFController.html
    new file mode 100644
    index 000000000..213c613e7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/RearrangePagesPDFController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RearrangePagesPDFController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">RearrangePagesPDFController</span></div><h1>RearrangePagesPDFController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">601 of 601</td><td class="ctr2">0%</td><td class="bar">67 of 67</td><td class="ctr2">0%</td><td class="ctr1">53</td><td class="ctr2">53</td><td class="ctr1">126</td><td class="ctr2">126</td><td class="ctr1">15</td><td class="ctr2">15</td></tr></tfoot><tbody><tr><td id="a7"><a href="RearrangePagesPDFController.java.html#L253" class="el_method">rearrangePages(RearrangePagesRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="122" alt="122"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">8</td><td class="ctr2" id="g1">8</td><td class="ctr1" id="h0">27</td><td class="ctr2" id="i0">27</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a13"><a href="RearrangePagesPDFController.java.html#L135" class="el_method">sideStitchBooklet(int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="63" alt="63"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h4">8</td><td class="ctr2" id="i4">8</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a6"><a href="RearrangePagesPDFController.java.html#L212" class="el_method">processSortTypes(String, int, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="62" alt="62"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="94" height="10" title="11" alt="11"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h1">16</td><td class="ctr2" id="i1">16</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="RearrangePagesPDFController.java.html#L54" class="el_method">deletePages(PDFWithPageNums)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="51" alt="51"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h3">13</td><td class="ctr2" id="i3">13</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="RearrangePagesPDFController.java.html#L180" class="el_method">duplicate(int, String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="49" alt="49"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="85" height="10" title="10" alt="10"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f2">6</td><td class="ctr2" id="g2">6</td><td class="ctr1" id="h2">14</td><td class="ctr2" id="i2">14</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a4"><a href="RearrangePagesPDFController.java.html#L166" class="el_method">oddEvenMerge(int)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="42" alt="42"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h5">7</td><td class="ctr2" id="i5">7</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="RearrangePagesPDFController.java.html#L114" class="el_method">duplexSort(int)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="38" alt="38"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h6">7</td><td class="ctr2" id="i6">7</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a5"><a href="RearrangePagesPDFController.java.html#L147" class="el_method">oddEvenSplit(int)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="34" alt="34"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h7">6</td><td class="ctr2" id="i7">6</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a0"><a href="RearrangePagesPDFController.java.html#L126" class="el_method">bookletSort(int)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="29" alt="29"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f11">2</td><td class="ctr2" id="g11">2</td><td class="ctr1" id="h8">5</td><td class="ctr2" id="i8">5</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a9"><a href="RearrangePagesPDFController.java.html#L79" class="el_method">removeFirst(int)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="27" alt="27"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f6">3</td><td class="ctr2" id="g6">3</td><td class="ctr1" id="h9">5</td><td class="ctr2" id="i9">5</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a11"><a href="RearrangePagesPDFController.java.html#L88" class="el_method">removeLast(int)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="27" alt="27"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f7">3</td><td class="ctr2" id="g7">3</td><td class="ctr1" id="h10">5</td><td class="ctr2" id="i10">5</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a10"><a href="RearrangePagesPDFController.java.html#L97" class="el_method">removeFirstAndLast(int)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="27" alt="27"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f8">3</td><td class="ctr2" id="g8">3</td><td class="ctr1" id="h11">5</td><td class="ctr2" id="i11">5</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a12"><a href="RearrangePagesPDFController.java.html#L106" class="el_method">reverseOrder(int)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="20" alt="20"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="2" alt="2"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">2</td><td class="ctr2" id="g12">2</td><td class="ctr1" id="h12">4</td><td class="ctr2" id="i12">4</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a8"><a href="RearrangePagesPDFController.java.html#L40" class="el_method">RearrangePagesPDFController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">3</td><td class="ctr2" id="i13">3</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a14"><a href="RearrangePagesPDFController.java.html#L33" class="el_method">static {...}</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h14">1</td><td class="ctr2" id="i14">1</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/RearrangePagesPDFController.java.html b/testResult/html/stirling.software.SPDF.controller.api/RearrangePagesPDFController.java.html
    new file mode 100644
    index 000000000..2c3240647
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/RearrangePagesPDFController.java.html
    @@ -0,0 +1,300 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RearrangePagesPDFController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">RearrangePagesPDFController.java</span></div><h1>RearrangePagesPDFController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.Collections;
    +import java.util.List;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.SortTypes;
    +import stirling.software.SPDF.model.api.PDFWithPageNums;
    +import stirling.software.SPDF.model.api.general.RearrangePagesRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +<span class="nc" id="L33">@Slf4j</span>
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class RearrangePagesPDFController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L40">    public RearrangePagesPDFController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L41">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L42">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/remove-pages&quot;)
    +    @Operation(
    +            summary = &quot;Remove pages from a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint removes specified pages from a given PDF file. Users can provide&quot;
    +                            + &quot; a comma-separated list of page numbers or ranges to delete. Input:PDF&quot;
    +                            + &quot; Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; deletePages(@ModelAttribute PDFWithPageNums request)
    +            throws IOException {
    +
    +<span class="nc" id="L54">        MultipartFile pdfFile = request.getFileInput();</span>
    +<span class="nc" id="L55">        String pagesToDelete = request.getPageNumbers();</span>
    +
    +<span class="nc" id="L57">        PDDocument document = pdfDocumentFactory.load(pdfFile);</span>
    +
    +        // Split the page order string into an array of page numbers or range of numbers
    +<span class="nc" id="L60">        String[] pageOrderArr = pagesToDelete.split(&quot;,&quot;);</span>
    +
    +<span class="nc" id="L62">        List&lt;Integer&gt; pagesToRemove =</span>
    +<span class="nc" id="L63">                GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages(), false);</span>
    +
    +<span class="nc" id="L65">        Collections.sort(pagesToRemove);</span>
    +
    +<span class="nc bnc" id="L67" title="All 2 branches missed.">        for (int i = pagesToRemove.size() - 1; i &gt;= 0; i--) {</span>
    +<span class="nc" id="L68">            int pageIndex = pagesToRemove.get(i);</span>
    +<span class="nc" id="L69">            document.removePage(pageIndex);</span>
    +        }
    +<span class="nc" id="L71">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L73">                Filenames.toSimpleFileName(pdfFile.getOriginalFilename())</span>
    +<span class="nc" id="L74">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_removed_pages.pdf&quot;);
    +    }
    +
    +    private List&lt;Integer&gt; removeFirst(int totalPages) {
    +<span class="nc bnc" id="L79" title="All 2 branches missed.">        if (totalPages &lt;= 1) return new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L80">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L81" title="All 2 branches missed.">        for (int i = 2; i &lt;= totalPages; i++) {</span>
    +<span class="nc" id="L82">            newPageOrder.add(i - 1);</span>
    +        }
    +<span class="nc" id="L84">        return newPageOrder;</span>
    +    }
    +
    +    private List&lt;Integer&gt; removeLast(int totalPages) {
    +<span class="nc bnc" id="L88" title="All 2 branches missed.">        if (totalPages &lt;= 1) return new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L89">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">        for (int i = 1; i &lt; totalPages; i++) {</span>
    +<span class="nc" id="L91">            newPageOrder.add(i - 1);</span>
    +        }
    +<span class="nc" id="L93">        return newPageOrder;</span>
    +    }
    +
    +    private List&lt;Integer&gt; removeFirstAndLast(int totalPages) {
    +<span class="nc bnc" id="L97" title="All 2 branches missed.">        if (totalPages &lt;= 2) return new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L98">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L99" title="All 2 branches missed.">        for (int i = 2; i &lt; totalPages; i++) {</span>
    +<span class="nc" id="L100">            newPageOrder.add(i - 1);</span>
    +        }
    +<span class="nc" id="L102">        return newPageOrder;</span>
    +    }
    +
    +    private List&lt;Integer&gt; reverseOrder(int totalPages) {
    +<span class="nc" id="L106">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L107" title="All 2 branches missed.">        for (int i = totalPages; i &gt;= 1; i--) {</span>
    +<span class="nc" id="L108">            newPageOrder.add(i - 1);</span>
    +        }
    +<span class="nc" id="L110">        return newPageOrder;</span>
    +    }
    +
    +    private List&lt;Integer&gt; duplexSort(int totalPages) {
    +<span class="nc" id="L114">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L115">        int half = (totalPages + 1) / 2; // This ensures proper behavior with odd numbers of pages</span>
    +<span class="nc bnc" id="L116" title="All 2 branches missed.">        for (int i = 1; i &lt;= half; i++) {</span>
    +<span class="nc" id="L117">            newPageOrder.add(i - 1);</span>
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">            if (i &lt;= totalPages - half) { // Avoid going out of bounds</span>
    +<span class="nc" id="L119">                newPageOrder.add(totalPages - i);</span>
    +            }
    +        }
    +<span class="nc" id="L122">        return newPageOrder;</span>
    +    }
    +
    +    private List&lt;Integer&gt; bookletSort(int totalPages) {
    +<span class="nc" id="L126">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">        for (int i = 0; i &lt; totalPages / 2; i++) {</span>
    +<span class="nc" id="L128">            newPageOrder.add(i);</span>
    +<span class="nc" id="L129">            newPageOrder.add(totalPages - i - 1);</span>
    +        }
    +<span class="nc" id="L131">        return newPageOrder;</span>
    +    }
    +
    +    private List&lt;Integer&gt; sideStitchBooklet(int totalPages) {
    +<span class="nc" id="L135">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L136" title="All 2 branches missed.">        for (int i = 0; i &lt; (totalPages + 3) / 4; i++) {</span>
    +<span class="nc" id="L137">            int begin = i * 4;</span>
    +<span class="nc" id="L138">            newPageOrder.add(Math.min(begin + 3, totalPages - 1));</span>
    +<span class="nc" id="L139">            newPageOrder.add(Math.min(begin, totalPages - 1));</span>
    +<span class="nc" id="L140">            newPageOrder.add(Math.min(begin + 1, totalPages - 1));</span>
    +<span class="nc" id="L141">            newPageOrder.add(Math.min(begin + 2, totalPages - 1));</span>
    +        }
    +<span class="nc" id="L143">        return newPageOrder;</span>
    +    }
    +
    +    private List&lt;Integer&gt; oddEvenSplit(int totalPages) {
    +<span class="nc" id="L147">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">        for (int i = 1; i &lt;= totalPages; i += 2) {</span>
    +<span class="nc" id="L149">            newPageOrder.add(i - 1);</span>
    +        }
    +<span class="nc bnc" id="L151" title="All 2 branches missed.">        for (int i = 2; i &lt;= totalPages; i += 2) {</span>
    +<span class="nc" id="L152">            newPageOrder.add(i - 1);</span>
    +        }
    +<span class="nc" id="L154">        return newPageOrder;</span>
    +    }
    +
    +    /**
    +     * Rearrange pages in a PDF file by merging odd and even pages. The first half of the pages will
    +     * be the odd pages, and the second half will be the even pages as input. &lt;br&gt;
    +     * This method is visible for testing purposes only.
    +     *
    +     * @param totalPages Total number of pages in the PDF file.
    +     * @return List of page numbers in the new order. The first page is 0.
    +     */
    +    List&lt;Integer&gt; oddEvenMerge(int totalPages) {
    +<span class="nc" id="L166">        List&lt;Integer&gt; newPageOrderZeroBased = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L167">        int numberOfOddPages = (totalPages + 1) / 2;</span>
    +
    +<span class="nc bnc" id="L169" title="All 2 branches missed.">        for (int oneBasedIndex = 1; oneBasedIndex &lt; (numberOfOddPages + 1); oneBasedIndex++) {</span>
    +<span class="nc" id="L170">            newPageOrderZeroBased.add((oneBasedIndex - 1));</span>
    +<span class="nc bnc" id="L171" title="All 2 branches missed.">            if (numberOfOddPages + oneBasedIndex &lt;= totalPages) {</span>
    +<span class="nc" id="L172">                newPageOrderZeroBased.add((numberOfOddPages + oneBasedIndex - 1));</span>
    +            }
    +        }
    +
    +<span class="nc" id="L176">        return newPageOrderZeroBased;</span>
    +    }
    +
    +    private List&lt;Integer&gt; duplicate(int totalPages, String pageOrder) {
    +<span class="nc" id="L180">        List&lt;Integer&gt; newPageOrder = new ArrayList&lt;&gt;();</span>
    +        int duplicateCount;
    +
    +        try {
    +            // Parse the duplicate count from pageOrder
    +            duplicateCount =
    +<span class="nc bnc" id="L186" title="All 4 branches missed.">                    pageOrder != null &amp;&amp; !pageOrder.isEmpty()</span>
    +<span class="nc" id="L187">                            ? Integer.parseInt(pageOrder.trim())</span>
    +<span class="nc" id="L188">                            : 2; // Default to 2 if not specified</span>
    +<span class="nc" id="L189">        } catch (NumberFormatException e) {</span>
    +<span class="nc" id="L190">            log.error(&quot;Invalid duplicate count specified&quot;, e);</span>
    +<span class="nc" id="L191">            duplicateCount = 2; // Default to 2 if invalid input</span>
    +<span class="nc" id="L192">        }</span>
    +
    +        // Validate duplicate count
    +<span class="nc bnc" id="L195" title="All 2 branches missed.">        if (duplicateCount &lt; 1) {</span>
    +<span class="nc" id="L196">            duplicateCount = 2; // Default to 2 if invalid input</span>
    +        }
    +
    +        // For each page in the document
    +<span class="nc bnc" id="L200" title="All 2 branches missed.">        for (int pageNum = 0; pageNum &lt; totalPages; pageNum++) {</span>
    +            // Add the current page index duplicateCount times
    +<span class="nc bnc" id="L202" title="All 2 branches missed.">            for (int dupCount = 0; dupCount &lt; duplicateCount; dupCount++) {</span>
    +<span class="nc" id="L203">                newPageOrder.add(pageNum);</span>
    +            }
    +        }
    +
    +<span class="nc" id="L207">        return newPageOrder;</span>
    +    }
    +
    +    private List&lt;Integer&gt; processSortTypes(String sortTypes, int totalPages, String pageOrder) {
    +        try {
    +<span class="nc" id="L212">            SortTypes mode = SortTypes.valueOf(sortTypes.toUpperCase());</span>
    +<span class="nc bnc" id="L213" title="All 11 branches missed.">            switch (mode) {</span>
    +                case REVERSE_ORDER:
    +<span class="nc" id="L215">                    return reverseOrder(totalPages);</span>
    +                case DUPLEX_SORT:
    +<span class="nc" id="L217">                    return duplexSort(totalPages);</span>
    +                case BOOKLET_SORT:
    +<span class="nc" id="L219">                    return bookletSort(totalPages);</span>
    +                case SIDE_STITCH_BOOKLET_SORT:
    +<span class="nc" id="L221">                    return sideStitchBooklet(totalPages);</span>
    +                case ODD_EVEN_SPLIT:
    +<span class="nc" id="L223">                    return oddEvenSplit(totalPages);</span>
    +                case ODD_EVEN_MERGE:
    +<span class="nc" id="L225">                    return oddEvenMerge(totalPages);</span>
    +                case REMOVE_FIRST:
    +<span class="nc" id="L227">                    return removeFirst(totalPages);</span>
    +                case REMOVE_LAST:
    +<span class="nc" id="L229">                    return removeLast(totalPages);</span>
    +                case REMOVE_FIRST_AND_LAST:
    +<span class="nc" id="L231">                    return removeFirstAndLast(totalPages);</span>
    +                case DUPLICATE:
    +<span class="nc" id="L233">                    return duplicate(totalPages, pageOrder);</span>
    +                default:
    +<span class="nc" id="L235">                    throw new IllegalArgumentException(&quot;Unsupported custom mode&quot;);</span>
    +            }
    +<span class="nc" id="L237">        } catch (IllegalArgumentException e) {</span>
    +<span class="nc" id="L238">            log.error(&quot;Unsupported custom mode&quot;, e);</span>
    +<span class="nc" id="L239">            return null;</span>
    +        }
    +    }
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/rearrange-pages&quot;)
    +    @Operation(
    +            summary = &quot;Rearrange pages in a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint rearranges pages in a given PDF file based on the specified page&quot;
    +                            + &quot; order or custom mode. Users can provide a page order as a&quot;
    +                            + &quot; comma-separated list of page numbers or page ranges, or a custom mode.&quot;
    +                            + &quot; Input:PDF Output:PDF&quot;)
    +    public ResponseEntity&lt;byte[]&gt; rearrangePages(@ModelAttribute RearrangePagesRequest request)
    +            throws IOException {
    +<span class="nc" id="L253">        MultipartFile pdfFile = request.getFileInput();</span>
    +<span class="nc" id="L254">        String pageOrder = request.getPageNumbers();</span>
    +<span class="nc" id="L255">        String sortType = request.getCustomMode();</span>
    +        try {
    +            // Load the input PDF
    +<span class="nc" id="L258">            PDDocument document = pdfDocumentFactory.load(pdfFile);</span>
    +
    +            // Split the page order string into an array of page numbers or range of numbers
    +<span class="nc bnc" id="L261" title="All 2 branches missed.">            String[] pageOrderArr = pageOrder != null ? pageOrder.split(&quot;,&quot;) : new String[0];</span>
    +<span class="nc" id="L262">            int totalPages = document.getNumberOfPages();</span>
    +            List&lt;Integer&gt; newPageOrder;
    +<span class="nc bnc" id="L264" title="All 2 branches missed.">            if (sortType != null</span>
    +<span class="nc bnc" id="L265" title="All 2 branches missed.">                    &amp;&amp; sortType.length() &gt; 0</span>
    +<span class="nc bnc" id="L266" title="All 2 branches missed.">                    &amp;&amp; !&quot;custom&quot;.equals(sortType.toLowerCase())) {</span>
    +<span class="nc" id="L267">                newPageOrder = processSortTypes(sortType, totalPages, pageOrder);</span>
    +            } else {
    +<span class="nc" id="L269">                newPageOrder = GeneralUtils.parsePageList(pageOrderArr, totalPages, false);</span>
    +            }
    +<span class="nc" id="L271">            log.info(&quot;newPageOrder = &quot; + newPageOrder);</span>
    +<span class="nc" id="L272">            log.info(&quot;totalPages = &quot; + totalPages);</span>
    +            // Create a new list to hold the pages in the new order
    +<span class="nc" id="L274">            List&lt;PDPage&gt; newPages = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L275" title="All 2 branches missed.">            for (int i = 0; i &lt; newPageOrder.size(); i++) {</span>
    +<span class="nc" id="L276">                newPages.add(document.getPage(newPageOrder.get(i)));</span>
    +            }
    +
    +            // Remove all the pages from the original document
    +<span class="nc bnc" id="L280" title="All 2 branches missed.">            for (int i = document.getNumberOfPages() - 1; i &gt;= 0; i--) {</span>
    +<span class="nc" id="L281">                document.removePage(i);</span>
    +            }
    +
    +            // Add the pages in the new order
    +<span class="nc bnc" id="L285" title="All 2 branches missed.">            for (PDPage page : newPages) {</span>
    +<span class="nc" id="L286">                document.addPage(page);</span>
    +<span class="nc" id="L287">            }</span>
    +
    +<span class="nc" id="L289">            return WebResponseUtils.pdfDocToWebResponse(</span>
    +                    document,
    +<span class="nc" id="L291">                    Filenames.toSimpleFileName(pdfFile.getOriginalFilename())</span>
    +<span class="nc" id="L292">                                    .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                            + &quot;_rearranged.pdf&quot;);
    +<span class="nc" id="L294">        } catch (IOException e) {</span>
    +<span class="nc" id="L295">            log.error(&quot;Failed rearranging documents&quot;, e);</span>
    +<span class="nc" id="L296">            return null;</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/RotationController.html b/testResult/html/stirling.software.SPDF.controller.api/RotationController.html
    new file mode 100644
    index 000000000..fdf9ae9e2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/RotationController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RotationController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">RotationController</span></div><h1>RotationController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">58 of 58</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="RotationController.java.html#L44" class="el_method">rotatePDF(RotatePDFRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="52" alt="52"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="RotationController.java.html#L32" class="el_method">RotationController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/RotationController.java.html b/testResult/html/stirling.software.SPDF.controller.api/RotationController.java.html
    new file mode 100644
    index 000000000..14702ceb5
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/RotationController.java.html
    @@ -0,0 +1,69 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RotationController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">RotationController.java</span></div><h1>RotationController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageTree;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.general.RotatePDFRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class RotationController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L32">    public RotationController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L33">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L34">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/rotate-pdf&quot;)
    +    @Operation(
    +            summary = &quot;Rotate a PDF file&quot;,
    +            description =
    +                    &quot;This endpoint rotates a given PDF file by a specified angle. The angle must be&quot;
    +                            + &quot; a multiple of 90. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; rotatePDF(@ModelAttribute RotatePDFRequest request)
    +            throws IOException {
    +<span class="nc" id="L44">        MultipartFile pdfFile = request.getFileInput();</span>
    +<span class="nc" id="L45">        Integer angle = request.getAngle();</span>
    +
    +        // Validate the angle is a multiple of 90
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">        if (angle % 90 != 0) {</span>
    +<span class="nc" id="L49">            throw new IllegalArgumentException(&quot;Angle must be a multiple of 90&quot;);</span>
    +        }
    +
    +        // Load the PDF document
    +<span class="nc" id="L53">        PDDocument document = pdfDocumentFactory.load(request);</span>
    +
    +        // Get the list of pages in the document
    +<span class="nc" id="L56">        PDPageTree pages = document.getPages();</span>
    +
    +<span class="nc bnc" id="L58" title="All 2 branches missed.">        for (PDPage page : pages) {</span>
    +<span class="nc" id="L59">            page.setRotation(page.getRotation() + angle);</span>
    +<span class="nc" id="L60">        }</span>
    +
    +<span class="nc" id="L62">        return WebResponseUtils.pdfDocToWebResponse(</span>
    +                document,
    +<span class="nc" id="L64">                Filenames.toSimpleFileName(pdfFile.getOriginalFilename())</span>
    +<span class="nc" id="L65">                                .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_rotated.pdf&quot;);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/ScalePagesController.html b/testResult/html/stirling.software.SPDF.controller.api/ScalePagesController.html
    new file mode 100644
    index 000000000..2287e83f0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/ScalePagesController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ScalePagesController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">ScalePagesController</span></div><h1>ScalePagesController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">239 of 239</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">58</td><td class="ctr2">58</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="ScalePagesController.java.html#L51" class="el_method">scalePages(ScalePagesRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="147" alt="147"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h0">34</td><td class="ctr2" id="i0">34</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ScalePagesController.java.html#L132" class="el_method">getSizeMap()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="51" alt="51"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">11</td><td class="ctr2" id="i1">11</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ScalePagesController.java.html#L108" class="el_method">getTargetSize(String, PDDocument)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="35" alt="35"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h2">10</td><td class="ctr2" id="i2">10</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="ScalePagesController.java.html#L39" class="el_method">ScalePagesController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/ScalePagesController.java.html b/testResult/html/stirling.software.SPDF.controller.api/ScalePagesController.java.html
    new file mode 100644
    index 000000000..76a62fa85
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/ScalePagesController.java.html
    @@ -0,0 +1,149 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ScalePagesController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">ScalePagesController.java</span></div><h1>ScalePagesController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.util.HashMap;
    +import java.util.Map;
    +
    +import org.apache.pdfbox.multipdf.LayerUtility;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.apache.pdfbox.util.Matrix;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.general.ScalePagesRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class ScalePagesController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L39">    public ScalePagesController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L40">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    @PostMapping(value = &quot;/scale-pages&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Change the size of a PDF page/document&quot;,
    +            description =
    +                    &quot;This operation takes an input PDF file and the size to scale the pages to in&quot;
    +                            + &quot; the output PDF file. Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; scalePages(@ModelAttribute ScalePagesRequest request)
    +            throws IOException {
    +<span class="nc" id="L51">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L52">        String targetPDRectangle = request.getPageSize();</span>
    +<span class="nc" id="L53">        float scaleFactor = request.getScaleFactor();</span>
    +
    +<span class="nc" id="L55">        PDDocument sourceDocument = pdfDocumentFactory.load(file);</span>
    +<span class="nc" id="L56">        PDDocument outputDocument =</span>
    +<span class="nc" id="L57">                pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);</span>
    +
    +<span class="nc" id="L59">        PDRectangle targetSize = getTargetSize(targetPDRectangle, sourceDocument);</span>
    +
    +<span class="nc" id="L61">        int totalPages = sourceDocument.getNumberOfPages();</span>
    +<span class="nc bnc" id="L62" title="All 2 branches missed.">        for (int i = 0; i &lt; totalPages; i++) {</span>
    +<span class="nc" id="L63">            PDPage sourcePage = sourceDocument.getPage(i);</span>
    +<span class="nc" id="L64">            PDRectangle sourceSize = sourcePage.getMediaBox();</span>
    +
    +<span class="nc" id="L66">            float scaleWidth = targetSize.getWidth() / sourceSize.getWidth();</span>
    +<span class="nc" id="L67">            float scaleHeight = targetSize.getHeight() / sourceSize.getHeight();</span>
    +<span class="nc" id="L68">            float scale = Math.min(scaleWidth, scaleHeight) * scaleFactor;</span>
    +
    +<span class="nc" id="L70">            PDPage newPage = new PDPage(targetSize);</span>
    +<span class="nc" id="L71">            outputDocument.addPage(newPage);</span>
    +
    +<span class="nc" id="L73">            PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(
    +                            outputDocument,
    +                            newPage,
    +                            PDPageContentStream.AppendMode.APPEND,
    +                            true,
    +                            true);
    +
    +<span class="nc" id="L81">            float x = (targetSize.getWidth() - sourceSize.getWidth() * scale) / 2;</span>
    +<span class="nc" id="L82">            float y = (targetSize.getHeight() - sourceSize.getHeight() * scale) / 2;</span>
    +
    +<span class="nc" id="L84">            contentStream.saveGraphicsState();</span>
    +<span class="nc" id="L85">            contentStream.transform(Matrix.getTranslateInstance(x, y));</span>
    +<span class="nc" id="L86">            contentStream.transform(Matrix.getScaleInstance(scale, scale));</span>
    +
    +<span class="nc" id="L88">            LayerUtility layerUtility = new LayerUtility(outputDocument);</span>
    +<span class="nc" id="L89">            PDFormXObject form = layerUtility.importPageAsForm(sourceDocument, i);</span>
    +<span class="nc" id="L90">            contentStream.drawForm(form);</span>
    +
    +<span class="nc" id="L92">            contentStream.restoreGraphicsState();</span>
    +<span class="nc" id="L93">            contentStream.close();</span>
    +        }
    +
    +<span class="nc" id="L96">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L97">        outputDocument.save(baos);</span>
    +<span class="nc" id="L98">        outputDocument.close();</span>
    +<span class="nc" id="L99">        sourceDocument.close();</span>
    +
    +<span class="nc" id="L101">        return WebResponseUtils.bytesToWebResponse(</span>
    +<span class="nc" id="L102">                baos.toByteArray(),</span>
    +<span class="nc" id="L103">                Filenames.toSimpleFileName(file.getOriginalFilename()).replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_scaled.pdf&quot;);
    +    }
    +
    +    private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">        if (&quot;KEEP&quot;.equals(targetPDRectangle)) {</span>
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">            if (sourceDocument.getNumberOfPages() == 0) {</span>
    +<span class="nc" id="L110">                return null;</span>
    +            }
    +
    +            // use the first page to determine the target page size
    +<span class="nc" id="L114">            PDPage sourcePage = sourceDocument.getPage(0);</span>
    +<span class="nc" id="L115">            PDRectangle sourceSize = sourcePage.getMediaBox();</span>
    +
    +<span class="nc" id="L117">            return sourceSize;</span>
    +        }
    +
    +<span class="nc" id="L120">        Map&lt;String, PDRectangle&gt; sizeMap = getSizeMap();</span>
    +
    +<span class="nc bnc" id="L122" title="All 2 branches missed.">        if (sizeMap.containsKey(targetPDRectangle)) {</span>
    +<span class="nc" id="L123">            return sizeMap.get(targetPDRectangle);</span>
    +        }
    +
    +<span class="nc" id="L126">        throw new IllegalArgumentException(</span>
    +                &quot;Invalid PDRectangle. It must be one of the following: A0, A1, A2, A3, A4, A5, A6,&quot;
    +                        + &quot; LETTER, LEGAL, KEEP&quot;);
    +    }
    +
    +    private Map&lt;String, PDRectangle&gt; getSizeMap() {
    +<span class="nc" id="L132">        Map&lt;String, PDRectangle&gt; sizeMap = new HashMap&lt;&gt;();</span>
    +        // Add A0 - A6
    +<span class="nc" id="L134">        sizeMap.put(&quot;A0&quot;, PDRectangle.A0);</span>
    +<span class="nc" id="L135">        sizeMap.put(&quot;A1&quot;, PDRectangle.A1);</span>
    +<span class="nc" id="L136">        sizeMap.put(&quot;A2&quot;, PDRectangle.A2);</span>
    +<span class="nc" id="L137">        sizeMap.put(&quot;A3&quot;, PDRectangle.A3);</span>
    +<span class="nc" id="L138">        sizeMap.put(&quot;A4&quot;, PDRectangle.A4);</span>
    +<span class="nc" id="L139">        sizeMap.put(&quot;A5&quot;, PDRectangle.A5);</span>
    +<span class="nc" id="L140">        sizeMap.put(&quot;A6&quot;, PDRectangle.A6);</span>
    +
    +        // Add other sizes
    +<span class="nc" id="L143">        sizeMap.put(&quot;LETTER&quot;, PDRectangle.LETTER);</span>
    +<span class="nc" id="L144">        sizeMap.put(&quot;LEGAL&quot;, PDRectangle.LEGAL);</span>
    +
    +<span class="nc" id="L146">        return sizeMap;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SettingsController.html b/testResult/html/stirling.software.SPDF.controller.api/SettingsController.html
    new file mode 100644
    index 000000000..325bfa5aa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SettingsController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SettingsController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">SettingsController</span></div><h1>SettingsController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">28 of 28</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="SettingsController.java.html#L34" class="el_method">updateApiKey(Boolean)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="SettingsController.java.html#L27" class="el_method">SettingsController(ApplicationProperties)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SettingsController.java.html b/testResult/html/stirling.software.SPDF.controller.api/SettingsController.java.html
    new file mode 100644
    index 000000000..4d9f2ba8f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SettingsController.java.html
    @@ -0,0 +1,45 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SettingsController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">SettingsController.java</span></div><h1>SettingsController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.IOException;
    +
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.stereotype.Controller;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestBody;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +
    +@Controller
    +@Tag(name = &quot;Settings&quot;, description = &quot;Settings APIs&quot;)
    +@RequestMapping(&quot;/api/v1/settings&quot;)
    +@Hidden
    +public class SettingsController {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L27">    public SettingsController(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L28">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L29">    }</span>
    +
    +    @PostMapping(&quot;/update-enable-analytics&quot;)
    +    @Hidden
    +    public ResponseEntity&lt;String&gt; updateApiKey(@RequestBody Boolean enabled) throws IOException {
    +<span class="nc bnc" id="L34" title="All 2 branches missed.">        if (applicationProperties.getSystem().getEnableAnalytics() != null) {</span>
    +<span class="nc" id="L35">            return ResponseEntity.status(HttpStatus.ALREADY_REPORTED)</span>
    +<span class="nc" id="L36">                    .body(</span>
    +                            &quot;Setting has already been set, To adjust please edit &quot;
    +<span class="nc" id="L38">                                    + InstallationPathConfig.getSettingsPath());</span>
    +        }
    +<span class="nc" id="L40">        GeneralUtils.saveKeyToSettings(&quot;system.enableAnalytics&quot;, enabled);</span>
    +<span class="nc" id="L41">        applicationProperties.getSystem().setEnableAnalytics(enabled);</span>
    +<span class="nc" id="L42">        return ResponseEntity.ok(&quot;Updated&quot;);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SplitPDFController.html b/testResult/html/stirling.software.SPDF.controller.api/SplitPDFController.html
    new file mode 100644
    index 000000000..3d1d202ec
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SplitPDFController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SplitPDFController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">SplitPDFController</span></div><h1>SplitPDFController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">250 of 250</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">69</td><td class="ctr2">69</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="SplitPDFController.java.html#L58" class="el_method">splitPdf(PDFWithPageNums)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="240" alt="240"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">9</td><td class="ctr2" id="g0">9</td><td class="ctr1" id="h0">65</td><td class="ctr2" id="i0">65</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="SplitPDFController.java.html#L43" class="el_method">SplitPDFController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="SplitPDFController.java.html#L36" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SplitPDFController.java.html b/testResult/html/stirling.software.SPDF.controller.api/SplitPDFController.java.html
    new file mode 100644
    index 000000000..baae0e070
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SplitPDFController.java.html
    @@ -0,0 +1,168 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SplitPDFController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">SplitPDFController.java</span></div><h1>SplitPDFController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.stream.Collectors;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.PDFWithPageNums;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +<span class="nc" id="L36">@Slf4j</span>
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class SplitPDFController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L43">    public SplitPDFController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L44">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L45">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/split-pages&quot;)
    +    @Operation(
    +            summary = &quot;Split a PDF file into separate documents&quot;,
    +            description =
    +                    &quot;This endpoint splits a given PDF file into separate documents based on the&quot;
    +                            + &quot; specified page numbers or ranges. Users can specify pages using&quot;
    +                            + &quot; individual numbers, ranges, or 'all' for every page. Input:PDF&quot;
    +                            + &quot; Output:PDF Type:SIMO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; splitPdf(@ModelAttribute PDFWithPageNums request)
    +            throws IOException {
    +
    +<span class="nc" id="L58">        PDDocument document = null;</span>
    +<span class="nc" id="L59">        Path zipFile = null;</span>
    +<span class="nc" id="L60">        List&lt;ByteArrayOutputStream&gt; splitDocumentsBoas = new ArrayList&lt;&gt;();</span>
    +
    +        try {
    +
    +<span class="nc" id="L64">            MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L65">            String pages = request.getPageNumbers();</span>
    +            // open the pdf document
    +
    +<span class="nc" id="L68">            document = pdfDocumentFactory.load(file);</span>
    +            // PdfMetadata metadata = PdfMetadataService.extractMetadataFromPdf(document);
    +<span class="nc" id="L70">            int totalPages = document.getNumberOfPages();</span>
    +<span class="nc" id="L71">            List&lt;Integer&gt; pageNumbers = request.getPageNumbersList(document, false);</span>
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">            if (!pageNumbers.contains(totalPages - 1)) {</span>
    +                // Create a mutable ArrayList so we can add to it
    +<span class="nc" id="L74">                pageNumbers = new ArrayList&lt;&gt;(pageNumbers);</span>
    +<span class="nc" id="L75">                pageNumbers.add(totalPages - 1);</span>
    +            }
    +
    +<span class="nc" id="L78">            log.info(</span>
    +                    &quot;Splitting PDF into pages: {}&quot;,
    +<span class="nc" id="L80">                    pageNumbers.stream().map(String::valueOf).collect(Collectors.joining(&quot;,&quot;)));</span>
    +
    +            // split the document
    +<span class="nc" id="L83">            splitDocumentsBoas = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L84">            int previousPageNumber = 0;</span>
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">            for (int splitPoint : pageNumbers) {</span>
    +<span class="nc" id="L86">                try (PDDocument splitDocument =</span>
    +<span class="nc" id="L87">                        pdfDocumentFactory.createNewDocumentBasedOnOldDocument(document)) {</span>
    +<span class="nc bnc" id="L88" title="All 2 branches missed.">                    for (int i = previousPageNumber; i &lt;= splitPoint; i++) {</span>
    +<span class="nc" id="L89">                        PDPage page = document.getPage(i);</span>
    +<span class="nc" id="L90">                        splitDocument.addPage(page);</span>
    +<span class="nc" id="L91">                        log.info(&quot;Adding page {} to split document&quot;, i);</span>
    +                    }
    +<span class="nc" id="L93">                    previousPageNumber = splitPoint + 1;</span>
    +
    +                    // Transfer metadata to split pdf
    +                    // PdfMetadataService.setMetadataToPdf(splitDocument, metadata);
    +
    +<span class="nc" id="L98">                    ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L99">                    splitDocument.save(baos);</span>
    +
    +<span class="nc" id="L101">                    splitDocumentsBoas.add(baos);</span>
    +<span class="nc" id="L102">                } catch (Exception e) {</span>
    +<span class="nc" id="L103">                    log.error(&quot;Failed splitting documents and saving them&quot;, e);</span>
    +<span class="nc" id="L104">                    throw e;</span>
    +<span class="nc" id="L105">                }</span>
    +<span class="nc" id="L106">            }</span>
    +
    +            // closing the original document
    +<span class="nc" id="L109">            document.close();</span>
    +
    +<span class="nc" id="L111">            zipFile = Files.createTempFile(&quot;split_documents&quot;, &quot;.zip&quot;);</span>
    +
    +<span class="nc" id="L113">            String filename =</span>
    +<span class="nc" id="L114">                    Filenames.toSimpleFileName(file.getOriginalFilename())</span>
    +<span class="nc" id="L115">                            .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L116">            try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {</span>
    +                // loop through the split documents and write them to the zip file
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">                for (int i = 0; i &lt; splitDocumentsBoas.size(); i++) {</span>
    +<span class="nc" id="L119">                    String fileName = filename + &quot;_&quot; + (i + 1) + &quot;.pdf&quot;;</span>
    +<span class="nc" id="L120">                    ByteArrayOutputStream baos = splitDocumentsBoas.get(i);</span>
    +<span class="nc" id="L121">                    byte[] pdf = baos.toByteArray();</span>
    +
    +                    // Add PDF file to the zip
    +<span class="nc" id="L124">                    ZipEntry pdfEntry = new ZipEntry(fileName);</span>
    +<span class="nc" id="L125">                    zipOut.putNextEntry(pdfEntry);</span>
    +<span class="nc" id="L126">                    zipOut.write(pdf);</span>
    +<span class="nc" id="L127">                    zipOut.closeEntry();</span>
    +
    +<span class="nc" id="L129">                    log.info(&quot;Wrote split document {} to zip file&quot;, fileName);</span>
    +                }
    +<span class="nc" id="L131">            } catch (Exception e) {</span>
    +<span class="nc" id="L132">                log.error(&quot;Failed writing to zip&quot;, e);</span>
    +<span class="nc" id="L133">                throw e;</span>
    +<span class="nc" id="L134">            }</span>
    +
    +<span class="nc" id="L136">            log.info(&quot;Successfully created zip file with split documents: {}&quot;, zipFile.toString());</span>
    +<span class="nc" id="L137">            byte[] data = Files.readAllBytes(zipFile);</span>
    +<span class="nc" id="L138">            Files.deleteIfExists(zipFile);</span>
    +
    +            // return the Resource in the response
    +<span class="nc" id="L141">            return WebResponseUtils.bytesToWebResponse(</span>
    +                    data, filename + &quot;.zip&quot;, MediaType.APPLICATION_OCTET_STREAM);
    +
    +        } finally {
    +            try {
    +                // Close the main document
    +<span class="nc bnc" id="L147" title="All 2 branches missed.">                if (document != null) {</span>
    +<span class="nc" id="L148">                    document.close();</span>
    +                }
    +
    +                // Close all ByteArrayOutputStreams
    +<span class="nc bnc" id="L152" title="All 2 branches missed.">                for (ByteArrayOutputStream baos : splitDocumentsBoas) {</span>
    +<span class="nc bnc" id="L153" title="All 2 branches missed.">                    if (baos != null) {</span>
    +<span class="nc" id="L154">                        baos.close();</span>
    +                    }
    +<span class="nc" id="L156">                }</span>
    +
    +                // Delete temporary zip file
    +<span class="nc bnc" id="L159" title="All 2 branches missed.">                if (zipFile != null) {</span>
    +<span class="nc" id="L160">                    Files.deleteIfExists(zipFile);</span>
    +                }
    +<span class="nc" id="L162">            } catch (Exception e) {</span>
    +<span class="nc" id="L163">                log.error(&quot;Error while cleaning up resources&quot;, e);</span>
    +<span class="nc" id="L164">            }</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SplitPdfByChaptersController.html b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfByChaptersController.html
    new file mode 100644
    index 000000000..98b419763
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfByChaptersController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SplitPdfByChaptersController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">SplitPdfByChaptersController</span></div><h1>SplitPdfByChaptersController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">562 of 562</td><td class="ctr2">0%</td><td class="bar">54 of 54</td><td class="ctr2">0%</td><td class="ctr1">34</td><td class="ctr2">34</td><td class="ctr1">148</td><td class="ctr2">148</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a4"><a href="SplitPdfByChaptersController.java.html#L131" class="el_method">splitPdf(SplitPdfByChaptersRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="186" alt="186"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">7</td><td class="ctr2" id="g1">7</td><td class="ctr1" id="h0">52</td><td class="ctr2" id="i0">52</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="SplitPdfByChaptersController.java.html#L66" class="el_method">extractOutlineItems(PDDocument, PDOutlineItem, List, PDOutlineItem, int, int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="76" height="10" title="119" alt="119"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h1">31</td><td class="ctr2" id="i1">31</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="SplitPdfByChaptersController.java.html#L275" class="el_method">getSplitDocumentsBoas(PDDocument, List, boolean)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="59" height="10" title="92" alt="92"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="12" alt="12"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">7</td><td class="ctr2" id="g2">7</td><td class="ctr1" id="h2">24</td><td class="ctr2" id="i2">24</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="SplitPdfByChaptersController.java.html#L242" class="el_method">createZipFile(List, List)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="83" alt="83"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h3">20</td><td class="ctr2" id="i3">20</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="SplitPdfByChaptersController.java.html#L215" class="el_method">mergeBookmarksThatCorrespondToSamePage(List)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="44" height="10" title="69" alt="69"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="8" alt="8"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">5</td><td class="ctr2" id="g3">5</td><td class="ctr1" id="h4">16</td><td class="ctr2" id="i4">16</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="SplitPdfByChaptersController.java.html#L52" class="el_method">SplitPdfByChaptersController(PdfMetadataService, CustomPDFDocumentFactory)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="9" alt="9"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">4</td><td class="ctr2" id="i5">4</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="SplitPdfByChaptersController.java.html#L42" class="el_method">static {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SplitPdfByChaptersController.java.html b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfByChaptersController.java.html
    new file mode 100644
    index 000000000..a079cc95e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfByChaptersController.java.html
    @@ -0,0 +1,317 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SplitPdfByChaptersController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">SplitPdfByChaptersController.java</span></div><h1>SplitPdfByChaptersController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
    +import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.AllArgsConstructor;
    +import lombok.Data;
    +import lombok.EqualsAndHashCode;
    +import lombok.NoArgsConstructor;
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.PdfMetadata;
    +import stirling.software.SPDF.model.api.SplitPdfByChaptersRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.service.PdfMetadataService;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +<span class="nc" id="L42">@Slf4j</span>
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class SplitPdfByChaptersController {
    +
    +    private final PdfMetadataService pdfMetadataService;
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +    public SplitPdfByChaptersController(
    +<span class="nc" id="L52">            PdfMetadataService pdfMetadataService, CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L53">        this.pdfMetadataService = pdfMetadataService;</span>
    +<span class="nc" id="L54">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L55">    }</span>
    +
    +    private static List&lt;Bookmark&gt; extractOutlineItems(
    +            PDDocument sourceDocument,
    +            PDOutlineItem current,
    +            List&lt;Bookmark&gt; bookmarks,
    +            PDOutlineItem nextParent,
    +            int level,
    +            int maxLevel)
    +            throws Exception {
    +
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">        while (current != null) {</span>
    +
    +<span class="nc" id="L68">            String currentTitle = current.getTitle().replace(&quot;/&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L69">            int firstPage =</span>
    +<span class="nc" id="L70">                    sourceDocument.getPages().indexOf(current.findDestinationPage(sourceDocument));</span>
    +<span class="nc" id="L71">            PDOutlineItem child = current.getFirstChild();</span>
    +<span class="nc" id="L72">            PDOutlineItem nextSibling = current.getNextSibling();</span>
    +            int endPage;
    +<span class="nc bnc" id="L74" title="All 4 branches missed.">            if (child != null &amp;&amp; level &lt; maxLevel) {</span>
    +<span class="nc" id="L75">                endPage =</span>
    +                        sourceDocument
    +<span class="nc" id="L77">                                .getPages()</span>
    +<span class="nc" id="L78">                                .indexOf(child.findDestinationPage(sourceDocument));</span>
    +<span class="nc bnc" id="L79" title="All 2 branches missed.">            } else if (nextSibling != null) {</span>
    +<span class="nc" id="L80">                endPage =</span>
    +                        sourceDocument
    +<span class="nc" id="L82">                                .getPages()</span>
    +<span class="nc" id="L83">                                .indexOf(nextSibling.findDestinationPage(sourceDocument));</span>
    +<span class="nc bnc" id="L84" title="All 2 branches missed.">            } else if (nextParent != null) {</span>
    +
    +<span class="nc" id="L86">                endPage =</span>
    +                        sourceDocument
    +<span class="nc" id="L88">                                .getPages()</span>
    +<span class="nc" id="L89">                                .indexOf(nextParent.findDestinationPage(sourceDocument));</span>
    +            } else {
    +<span class="nc" id="L91">                endPage = -2;</span>
    +                /*
    +                happens when we have something like this:
    +                Outline Item 2
    +                    Outline Item 2.1
    +                        Outline Item 2.1.1
    +                    Outline Item 2.2
    +                        Outline 2.2.1
    +                        Outline 2.2.2 &lt;--- this item neither has an immediate next parent nor an immediate next sibling
    +                Outline Item 3
    +                 */
    +            }
    +<span class="nc bnc" id="L103" title="All 2 branches missed.">            if (!bookmarks.isEmpty()</span>
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">                    &amp;&amp; bookmarks.get(bookmarks.size() - 1).getEndPage() == -2</span>
    +                    &amp;&amp; firstPage
    +                            &gt;= bookmarks
    +<span class="nc" id="L107">                                    .get(bookmarks.size() - 1)</span>
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">                                    .getStartPage()) { // for handling the above-mentioned case</span>
    +<span class="nc" id="L109">                Bookmark previousBookmark = bookmarks.get(bookmarks.size() - 1);</span>
    +<span class="nc" id="L110">                previousBookmark.setEndPage(firstPage);</span>
    +            }
    +<span class="nc" id="L112">            bookmarks.add(new Bookmark(currentTitle, firstPage, endPage));</span>
    +
    +            // Recursively process children
    +<span class="nc bnc" id="L115" title="All 4 branches missed.">            if (child != null &amp;&amp; level &lt; maxLevel) {</span>
    +<span class="nc" id="L116">                extractOutlineItems(</span>
    +                        sourceDocument, child, bookmarks, nextSibling, level + 1, maxLevel);
    +            }
    +
    +<span class="nc" id="L120">            current = nextSibling;</span>
    +<span class="nc" id="L121">        }</span>
    +<span class="nc" id="L122">        return bookmarks;</span>
    +    }
    +
    +    @PostMapping(value = &quot;/split-pdf-by-chapters&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Split PDFs by Chapters&quot;,
    +            description = &quot;Splits a PDF into chapters and returns a ZIP file.&quot;)
    +    public ResponseEntity&lt;byte[]&gt; splitPdf(@ModelAttribute SplitPdfByChaptersRequest request)
    +            throws Exception {
    +<span class="nc" id="L131">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L132">        PDDocument sourceDocument = null;</span>
    +<span class="nc" id="L133">        Path zipFile = null;</span>
    +
    +        try {
    +<span class="nc" id="L136">            boolean includeMetadata = request.getIncludeMetadata();</span>
    +<span class="nc" id="L137">            Integer bookmarkLevel =</span>
    +<span class="nc" id="L138">                    request.getBookmarkLevel(); // levels start from 0 (top most bookmarks)</span>
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">            if (bookmarkLevel &lt; 0) {</span>
    +<span class="nc" id="L140">                return ResponseEntity.badRequest().body(&quot;Invalid bookmark level&quot;.getBytes());</span>
    +            }
    +<span class="nc" id="L142">            sourceDocument = pdfDocumentFactory.load(file);</span>
    +
    +<span class="nc" id="L144">            PDDocumentOutline outline = sourceDocument.getDocumentCatalog().getDocumentOutline();</span>
    +
    +<span class="nc bnc" id="L146" title="All 2 branches missed.">            if (outline == null) {</span>
    +<span class="nc" id="L147">                log.warn(&quot;No outline found for {}&quot;, file.getOriginalFilename());</span>
    +<span class="nc" id="L148">                return ResponseEntity.badRequest().body(&quot;No outline found&quot;.getBytes());</span>
    +            }
    +<span class="nc" id="L150">            List&lt;Bookmark&gt; bookmarks = new ArrayList&lt;&gt;();</span>
    +            try {
    +<span class="nc" id="L152">                bookmarks =</span>
    +<span class="nc" id="L153">                        extractOutlineItems(</span>
    +                                sourceDocument,
    +<span class="nc" id="L155">                                outline.getFirstChild(),</span>
    +                                bookmarks,
    +<span class="nc" id="L157">                                outline.getFirstChild().getNextSibling(),</span>
    +                                0,
    +<span class="nc" id="L159">                                bookmarkLevel);</span>
    +                // to handle last page edge case
    +<span class="nc" id="L161">                bookmarks.get(bookmarks.size() - 1).setEndPage(sourceDocument.getNumberOfPages());</span>
    +<span class="nc" id="L162">                Bookmark lastBookmark = bookmarks.get(bookmarks.size() - 1);</span>
    +
    +<span class="nc" id="L164">            } catch (Exception e) {</span>
    +<span class="nc" id="L165">                log.error(&quot;Unable to extract outline items&quot;, e);</span>
    +<span class="nc" id="L166">                return ResponseEntity.internalServerError()</span>
    +<span class="nc" id="L167">                        .body(&quot;Unable to extract outline items&quot;.getBytes());</span>
    +<span class="nc" id="L168">            }</span>
    +
    +<span class="nc" id="L170">            boolean allowDuplicates = request.getAllowDuplicates();</span>
    +<span class="nc bnc" id="L171" title="All 2 branches missed.">            if (!allowDuplicates) {</span>
    +                /*
    +                duplicates are generated when multiple bookmarks correspond to the same page,
    +                if the user doesn't want duplicates mergeBookmarksThatCorrespondToSamePage() method will merge the titles of all
    +                the bookmarks that correspond to the same page, and treat them as a single bookmark
    +                */
    +<span class="nc" id="L177">                bookmarks = mergeBookmarksThatCorrespondToSamePage(bookmarks);</span>
    +            }
    +<span class="nc bnc" id="L179" title="All 2 branches missed.">            for (Bookmark bookmark : bookmarks) {</span>
    +<span class="nc" id="L180">                log.info(</span>
    +                        &quot;{}::::{} to {}&quot;,
    +<span class="nc" id="L182">                        bookmark.getTitle(),</span>
    +<span class="nc" id="L183">                        bookmark.getStartPage(),</span>
    +<span class="nc" id="L184">                        bookmark.getEndPage());</span>
    +<span class="nc" id="L185">            }</span>
    +<span class="nc" id="L186">            List&lt;ByteArrayOutputStream&gt; splitDocumentsBoas =</span>
    +<span class="nc" id="L187">                    getSplitDocumentsBoas(sourceDocument, bookmarks, includeMetadata);</span>
    +
    +<span class="nc" id="L189">            zipFile = createZipFile(bookmarks, splitDocumentsBoas);</span>
    +
    +<span class="nc" id="L191">            byte[] data = Files.readAllBytes(zipFile);</span>
    +<span class="nc" id="L192">            Files.deleteIfExists(zipFile);</span>
    +
    +<span class="nc" id="L194">            String filename =</span>
    +<span class="nc" id="L195">                    Filenames.toSimpleFileName(file.getOriginalFilename())</span>
    +<span class="nc" id="L196">                            .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L197">            sourceDocument.close();</span>
    +<span class="nc" id="L198">            return WebResponseUtils.bytesToWebResponse(</span>
    +                    data, filename + &quot;.zip&quot;, MediaType.APPLICATION_OCTET_STREAM);
    +        } finally {
    +            try {
    +<span class="nc bnc" id="L202" title="All 2 branches missed.">                if (sourceDocument != null) {</span>
    +<span class="nc" id="L203">                    sourceDocument.close();</span>
    +                }
    +<span class="nc bnc" id="L205" title="All 2 branches missed.">                if (zipFile != null) {</span>
    +<span class="nc" id="L206">                    Files.deleteIfExists(zipFile);</span>
    +                }
    +<span class="nc" id="L208">            } catch (Exception e) {</span>
    +<span class="nc" id="L209">                log.error(&quot;Error while cleaning up resources&quot;, e);</span>
    +<span class="nc" id="L210">            }</span>
    +        }
    +    }
    +
    +    private List&lt;Bookmark&gt; mergeBookmarksThatCorrespondToSamePage(List&lt;Bookmark&gt; bookmarks) {
    +<span class="nc" id="L215">        String mergedTitle = &quot;&quot;;</span>
    +<span class="nc" id="L216">        List&lt;Bookmark&gt; chaptersToBeRemoved = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L217" title="All 2 branches missed.">        for (Bookmark bookmark : bookmarks) {</span>
    +<span class="nc bnc" id="L218" title="All 2 branches missed.">            if (bookmark.getStartPage() == bookmark.getEndPage()) {</span>
    +<span class="nc" id="L219">                mergedTitle = mergedTitle.concat(bookmark.getTitle().concat(&quot; &quot;));</span>
    +<span class="nc" id="L220">                chaptersToBeRemoved.add(bookmark);</span>
    +            } else {
    +<span class="nc bnc" id="L222" title="All 2 branches missed.">                if (!mergedTitle.isEmpty()) {</span>
    +<span class="nc bnc" id="L223" title="All 2 branches missed.">                    if (mergedTitle.length() &gt; 255) {</span>
    +<span class="nc" id="L224">                        mergedTitle = mergedTitle.substring(0, 253) + &quot;...&quot;;</span>
    +                    }
    +
    +<span class="nc" id="L227">                    bookmarks.set(</span>
    +<span class="nc" id="L228">                            bookmarks.indexOf(bookmark),</span>
    +                            new Bookmark(
    +<span class="nc" id="L230">                                    mergedTitle, bookmark.getStartPage(), bookmark.getEndPage()));</span>
    +                }
    +<span class="nc" id="L232">                mergedTitle = &quot;&quot;;</span>
    +            }
    +<span class="nc" id="L234">        }</span>
    +<span class="nc" id="L235">        bookmarks.removeAll(chaptersToBeRemoved);</span>
    +<span class="nc" id="L236">        return bookmarks;</span>
    +    }
    +
    +    private Path createZipFile(
    +            List&lt;Bookmark&gt; bookmarks, List&lt;ByteArrayOutputStream&gt; splitDocumentsBoas)
    +            throws Exception {
    +<span class="nc" id="L242">        Path zipFile = Files.createTempFile(&quot;split_documents&quot;, &quot;.zip&quot;);</span>
    +<span class="nc" id="L243">        String fileNumberFormatter = &quot;%0&quot; + (Integer.toString(bookmarks.size()).length()) + &quot;d &quot;;</span>
    +<span class="nc" id="L244">        try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {</span>
    +<span class="nc bnc" id="L245" title="All 2 branches missed.">            for (int i = 0; i &lt; splitDocumentsBoas.size(); i++) {</span>
    +
    +                // split files will be named as &quot;[FILE_NUMBER] [BOOKMARK_TITLE].pdf&quot;
    +
    +<span class="nc" id="L249">                String fileName =</span>
    +<span class="nc" id="L250">                        String.format(fileNumberFormatter, i)</span>
    +<span class="nc" id="L251">                                + bookmarks.get(i).getTitle()</span>
    +                                + &quot;.pdf&quot;;
    +<span class="nc" id="L253">                ByteArrayOutputStream baos = splitDocumentsBoas.get(i);</span>
    +<span class="nc" id="L254">                byte[] pdf = baos.toByteArray();</span>
    +
    +<span class="nc" id="L256">                ZipEntry pdfEntry = new ZipEntry(fileName);</span>
    +<span class="nc" id="L257">                zipOut.putNextEntry(pdfEntry);</span>
    +<span class="nc" id="L258">                zipOut.write(pdf);</span>
    +<span class="nc" id="L259">                zipOut.closeEntry();</span>
    +
    +<span class="nc" id="L261">                log.info(&quot;Wrote split document {} to zip file&quot;, fileName);</span>
    +            }
    +<span class="nc" id="L263">        } catch (Exception e) {</span>
    +<span class="nc" id="L264">            log.error(&quot;Failed writing to zip&quot;, e);</span>
    +<span class="nc" id="L265">            throw e;</span>
    +<span class="nc" id="L266">        }</span>
    +
    +<span class="nc" id="L268">        log.info(&quot;Successfully created zip file with split documents: {}&quot;, zipFile);</span>
    +<span class="nc" id="L269">        return zipFile;</span>
    +    }
    +
    +    public List&lt;ByteArrayOutputStream&gt; getSplitDocumentsBoas(
    +            PDDocument sourceDocument, List&lt;Bookmark&gt; bookmarks, boolean includeMetadata)
    +            throws Exception {
    +<span class="nc" id="L275">        List&lt;ByteArrayOutputStream&gt; splitDocumentsBoas = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L276">        PdfMetadata metadata = null;</span>
    +<span class="nc bnc" id="L277" title="All 2 branches missed.">        if (includeMetadata) {</span>
    +<span class="nc" id="L278">            metadata = pdfMetadataService.extractMetadataFromPdf(sourceDocument);</span>
    +        }
    +<span class="nc bnc" id="L280" title="All 2 branches missed.">        for (Bookmark bookmark : bookmarks) {</span>
    +<span class="nc" id="L281">            try (PDDocument splitDocument = new PDDocument()) {</span>
    +<span class="nc bnc" id="L282" title="All 2 branches missed.">                boolean isSinglePage = (bookmark.getStartPage() == bookmark.getEndPage());</span>
    +
    +<span class="nc" id="L284">                for (int i = bookmark.getStartPage();</span>
    +<span class="nc bnc" id="L285" title="All 4 branches missed.">                        i &lt; bookmark.getEndPage() + (isSinglePage ? 1 : 0);</span>
    +<span class="nc" id="L286">                        i++) {</span>
    +<span class="nc" id="L287">                    PDPage page = sourceDocument.getPage(i);</span>
    +<span class="nc" id="L288">                    splitDocument.addPage(page);</span>
    +<span class="nc" id="L289">                    log.info(&quot;Adding page {} to split document&quot;, i);</span>
    +                }
    +<span class="nc" id="L291">                ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc bnc" id="L292" title="All 2 branches missed.">                if (includeMetadata) {</span>
    +<span class="nc" id="L293">                    pdfMetadataService.setMetadataToPdf(splitDocument, metadata);</span>
    +                }
    +
    +<span class="nc" id="L296">                splitDocument.save(baos);</span>
    +
    +<span class="nc" id="L298">                splitDocumentsBoas.add(baos);</span>
    +<span class="nc" id="L299">            } catch (Exception e) {</span>
    +<span class="nc" id="L300">                log.error(&quot;Failed splitting documents and saving them&quot;, e);</span>
    +<span class="nc" id="L301">                throw e;</span>
    +<span class="nc" id="L302">            }</span>
    +<span class="nc" id="L303">        }</span>
    +<span class="nc" id="L304">        return splitDocumentsBoas;</span>
    +    }
    +}
    +
    +@Data
    +@EqualsAndHashCode
    +@NoArgsConstructor
    +@AllArgsConstructor
    +class Bookmark {
    +    private String title;
    +    private int startPage;
    +    private int endPage;
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySectionsController.html b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySectionsController.html
    new file mode 100644
    index 000000000..4790320a9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySectionsController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SplitPdfBySectionsController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">SplitPdfBySectionsController</span></div><h1>SplitPdfBySectionsController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">308 of 308</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">70</td><td class="ctr2">70</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="SplitPdfBySectionsController.java.html#L58" class="el_method">splitPdf(SplitPdfBySectionsRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="167" alt="167"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">39</td><td class="ctr2" id="i0">39</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="SplitPdfBySectionsController.java.html#L118" class="el_method">splitPdfPages(PDDocument, int, int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="97" height="10" title="135" alt="135"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h1">28</td><td class="ctr2" id="i1">28</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="SplitPdfBySectionsController.java.html#L45" class="el_method">SplitPdfBySectionsController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySectionsController.java.html b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySectionsController.java.html
    new file mode 100644
    index 000000000..a00aa3f4d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySectionsController.java.html
    @@ -0,0 +1,166 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SplitPdfBySectionsController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">SplitPdfBySectionsController.java</span></div><h1>SplitPdfBySectionsController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.pdfbox.multipdf.LayerUtility;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.apache.pdfbox.util.Matrix;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.SplitPdfBySectionsRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class SplitPdfBySectionsController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L45">    public SplitPdfBySectionsController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L46">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L47">    }</span>
    +
    +    @PostMapping(value = &quot;/split-pdf-by-sections&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Split PDF pages into smaller sections&quot;,
    +            description =
    +                    &quot;Split each page of a PDF into smaller sections based on the user's choice&quot;
    +                            + &quot; (halves, thirds, quarters, etc.), both vertically and horizontally.&quot;
    +                            + &quot; Input:PDF Output:ZIP-PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; splitPdf(@ModelAttribute SplitPdfBySectionsRequest request)
    +            throws Exception {
    +<span class="nc" id="L58">        List&lt;ByteArrayOutputStream&gt; splitDocumentsBoas = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc" id="L60">        MultipartFile file = request.getFileInput();</span>
    +<span class="nc" id="L61">        PDDocument sourceDocument = pdfDocumentFactory.load(file);</span>
    +
    +        // Process the PDF based on split parameters
    +<span class="nc" id="L64">        int horiz = request.getHorizontalDivisions() + 1;</span>
    +<span class="nc" id="L65">        int verti = request.getVerticalDivisions() + 1;</span>
    +<span class="nc" id="L66">        boolean merge = request.isMerge();</span>
    +<span class="nc" id="L67">        List&lt;PDDocument&gt; splitDocuments = splitPdfPages(sourceDocument, verti, horiz);</span>
    +
    +<span class="nc" id="L69">        String filename =</span>
    +<span class="nc" id="L70">                Filenames.toSimpleFileName(file.getOriginalFilename())</span>
    +<span class="nc" id="L71">                        .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">        if (merge) {</span>
    +<span class="nc" id="L73">            MergeController mergeController = new MergeController(pdfDocumentFactory);</span>
    +<span class="nc" id="L74">            ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L75">            mergeController.mergeDocuments(splitDocuments).save(baos);</span>
    +<span class="nc" id="L76">            return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + &quot;_split.pdf&quot;);</span>
    +        }
    +<span class="nc bnc" id="L78" title="All 2 branches missed.">        for (PDDocument doc : splitDocuments) {</span>
    +<span class="nc" id="L79">            ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L80">            doc.save(baos);</span>
    +<span class="nc" id="L81">            doc.close();</span>
    +<span class="nc" id="L82">            splitDocumentsBoas.add(baos);</span>
    +<span class="nc" id="L83">        }</span>
    +
    +<span class="nc" id="L85">        sourceDocument.close();</span>
    +
    +<span class="nc" id="L87">        Path zipFile = Files.createTempFile(&quot;split_documents&quot;, &quot;.zip&quot;);</span>
    +        byte[] data;
    +
    +<span class="nc" id="L90">        try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {</span>
    +<span class="nc" id="L91">            int pageNum = 1;</span>
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">            for (int i = 0; i &lt; splitDocumentsBoas.size(); i++) {</span>
    +<span class="nc" id="L93">                ByteArrayOutputStream baos = splitDocumentsBoas.get(i);</span>
    +<span class="nc" id="L94">                int sectionNum = (i % (horiz * verti)) + 1;</span>
    +<span class="nc" id="L95">                String fileName = filename + &quot;_&quot; + pageNum + &quot;_&quot; + sectionNum + &quot;.pdf&quot;;</span>
    +<span class="nc" id="L96">                byte[] pdf = baos.toByteArray();</span>
    +<span class="nc" id="L97">                ZipEntry pdfEntry = new ZipEntry(fileName);</span>
    +<span class="nc" id="L98">                zipOut.putNextEntry(pdfEntry);</span>
    +<span class="nc" id="L99">                zipOut.write(pdf);</span>
    +<span class="nc" id="L100">                zipOut.closeEntry();</span>
    +
    +<span class="nc bnc" id="L102" title="All 2 branches missed.">                if (sectionNum == horiz * verti) pageNum++;</span>
    +            }
    +
    +<span class="nc" id="L105">            zipOut.finish();</span>
    +<span class="nc" id="L106">            data = Files.readAllBytes(zipFile);</span>
    +<span class="nc" id="L107">            return WebResponseUtils.bytesToWebResponse(</span>
    +                    data, filename + &quot;_split.zip&quot;, MediaType.APPLICATION_OCTET_STREAM);
    +
    +        } finally {
    +<span class="nc" id="L111">            Files.deleteIfExists(zipFile);</span>
    +        }
    +    }
    +
    +    public List&lt;PDDocument&gt; splitPdfPages(
    +            PDDocument document, int horizontalDivisions, int verticalDivisions)
    +            throws IOException {
    +<span class="nc" id="L118">        List&lt;PDDocument&gt; splitDocuments = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L120" title="All 2 branches missed.">        for (PDPage originalPage : document.getPages()) {</span>
    +<span class="nc" id="L121">            PDRectangle originalMediaBox = originalPage.getMediaBox();</span>
    +<span class="nc" id="L122">            float width = originalMediaBox.getWidth();</span>
    +<span class="nc" id="L123">            float height = originalMediaBox.getHeight();</span>
    +<span class="nc" id="L124">            float subPageWidth = width / horizontalDivisions;</span>
    +<span class="nc" id="L125">            float subPageHeight = height / verticalDivisions;</span>
    +
    +<span class="nc" id="L127">            LayerUtility layerUtility = new LayerUtility(document);</span>
    +
    +<span class="nc bnc" id="L129" title="All 2 branches missed.">            for (int i = 0; i &lt; horizontalDivisions; i++) {</span>
    +<span class="nc bnc" id="L130" title="All 2 branches missed.">                for (int j = 0; j &lt; verticalDivisions; j++) {</span>
    +<span class="nc" id="L131">                    PDDocument subDoc = new PDDocument();</span>
    +<span class="nc" id="L132">                    PDPage subPage = new PDPage(new PDRectangle(subPageWidth, subPageHeight));</span>
    +<span class="nc" id="L133">                    subDoc.addPage(subPage);</span>
    +
    +<span class="nc" id="L135">                    PDFormXObject form =</span>
    +<span class="nc" id="L136">                            layerUtility.importPageAsForm(</span>
    +<span class="nc" id="L137">                                    document, document.getPages().indexOf(originalPage));</span>
    +
    +<span class="nc" id="L139">                    try (PDPageContentStream contentStream =</span>
    +                            new PDPageContentStream(
    +                                    subDoc, subPage, AppendMode.APPEND, true, true)) {
    +                        // Set clipping area and position
    +<span class="nc" id="L143">                        float translateX = -subPageWidth * i;</span>
    +
    +                        // float translateY = height - subPageHeight * (verticalDivisions - j);
    +<span class="nc" id="L146">                        float translateY = -subPageHeight * (verticalDivisions - 1 - j);</span>
    +
    +<span class="nc" id="L148">                        contentStream.saveGraphicsState();</span>
    +<span class="nc" id="L149">                        contentStream.addRect(0, 0, subPageWidth, subPageHeight);</span>
    +<span class="nc" id="L150">                        contentStream.clip();</span>
    +<span class="nc" id="L151">                        contentStream.transform(new Matrix(1, 0, 0, 1, translateX, translateY));</span>
    +
    +                        // Draw the form
    +<span class="nc" id="L154">                        contentStream.drawForm(form);</span>
    +<span class="nc" id="L155">                        contentStream.restoreGraphicsState();</span>
    +                    }
    +
    +<span class="nc" id="L158">                    splitDocuments.add(subDoc);</span>
    +                }
    +            }
    +<span class="nc" id="L161">        }</span>
    +
    +<span class="nc" id="L163">        return splitDocuments;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySizeController.html b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySizeController.html
    new file mode 100644
    index 000000000..1fdc2fbc1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySizeController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SplitPdfBySizeController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">SplitPdfBySizeController</span></div><h1>SplitPdfBySizeController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,142 of 1,142</td><td class="ctr2">0%</td><td class="bar">54 of 54</td><td class="ctr2">0%</td><td class="ctr1">34</td><td class="ctr2">34</td><td class="ctr1">284</td><td class="ctr2">284</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a3"><a href="SplitPdfBySizeController.java.html#L146" class="el_method">handleSplitBySize(PDDocument, long, ZipOutputStream, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="310" alt="310"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="34" alt="34"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">18</td><td class="ctr2" id="g0">18</td><td class="ctr1" id="h0">73</td><td class="ctr2" id="i0">73</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="SplitPdfBySizeController.java.html#L274" class="el_method">handleSplitByPageCount(PDDocument, int, ZipOutputStream, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="249" alt="249"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h1">72</td><td class="ctr2" id="i1">72</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="SplitPdfBySizeController.java.html#L378" class="el_method">handleSplitByDocCount(PDDocument, int, ZipOutputStream, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="89" height="10" title="231" alt="231"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">4</td><td class="ctr2" id="g3">4</td><td class="ctr1" id="h3">40</td><td class="ctr2" id="i3">40</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="SplitPdfBySizeController.java.html#L57" class="el_method">autoSplitPdf(SplitPdfBySizeOrCountRequest)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="89" height="10" title="230" alt="230"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h2">65</td><td class="ctr2" id="i2">65</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="SplitPdfBySizeController.java.html#L435" class="el_method">saveDocumentToZip(PDDocument, ZipOutputStream, String, int)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="112" alt="112"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">30</td><td class="ctr2" id="i4">30</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="SplitPdfBySizeController.java.html#L41" class="el_method">SplitPdfBySizeController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="6" alt="6"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a6"><a href="SplitPdfBySizeController.java.html#L34" class="el_method">static {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySizeController.java.html b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySizeController.java.html
    new file mode 100644
    index 000000000..63d50b13c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/SplitPdfBySizeController.java.html
    @@ -0,0 +1,476 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SplitPdfBySizeController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">SplitPdfBySizeController.java</span></div><h1>SplitPdfBySizeController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.general.SplitPdfBySizeOrCountRequest;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +<span class="nc" id="L34">@Slf4j</span>
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class SplitPdfBySizeController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L41">    public SplitPdfBySizeController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L42">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L43">    }</span>
    +
    +    @PostMapping(value = &quot;/split-by-size-or-count&quot;, consumes = &quot;multipart/form-data&quot;)
    +    @Operation(
    +            summary = &quot;Auto split PDF pages into separate documents based on size or count&quot;,
    +            description =
    +                    &quot;split PDF into multiple paged documents based on size/count, ie if 20 pages&quot;
    +                            + &quot; and split into 5, it does 5 documents each 4 pages\r\n&quot;
    +                            + &quot; if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB&quot;
    +                            + &quot; (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF&quot;
    +                            + &quot; Output:ZIP-PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request)
    +            throws Exception {
    +
    +<span class="nc" id="L57">        log.debug(&quot;Starting PDF split process with request: {}&quot;, request);</span>
    +<span class="nc" id="L58">        MultipartFile file = request.getFileInput();</span>
    +
    +<span class="nc" id="L60">        Path zipFile = Files.createTempFile(&quot;split_documents&quot;, &quot;.zip&quot;);</span>
    +<span class="nc" id="L61">        log.debug(&quot;Created temporary zip file: {}&quot;, zipFile);</span>
    +
    +<span class="nc" id="L63">        String filename =</span>
    +<span class="nc" id="L64">                Filenames.toSimpleFileName(file.getOriginalFilename())</span>
    +<span class="nc" id="L65">                        .replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L66">        log.debug(&quot;Base filename for output: {}&quot;, filename);</span>
    +
    +<span class="nc" id="L68">        byte[] data = null;</span>
    +        try {
    +<span class="nc" id="L70">            log.debug(&quot;Reading input file bytes&quot;);</span>
    +<span class="nc" id="L71">            byte[] pdfBytes = file.getBytes();</span>
    +<span class="nc" id="L72">            log.debug(&quot;Successfully read {} bytes from input file&quot;, pdfBytes.length);</span>
    +
    +<span class="nc" id="L74">            log.debug(&quot;Creating ZIP output stream&quot;);</span>
    +<span class="nc" id="L75">            try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {</span>
    +<span class="nc" id="L76">                log.debug(&quot;Loading PDF document&quot;);</span>
    +<span class="nc" id="L77">                try (PDDocument sourceDocument = pdfDocumentFactory.load(pdfBytes)) {</span>
    +<span class="nc" id="L78">                    log.debug(</span>
    +                            &quot;Successfully loaded PDF with {} pages&quot;,
    +<span class="nc" id="L80">                            sourceDocument.getNumberOfPages());</span>
    +
    +<span class="nc" id="L82">                    int type = request.getSplitType();</span>
    +<span class="nc" id="L83">                    String value = request.getSplitValue();</span>
    +<span class="nc" id="L84">                    log.debug(&quot;Split type: {}, Split value: {}&quot;, type, value);</span>
    +
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">                    if (type == 0) {</span>
    +<span class="nc" id="L87">                        log.debug(&quot;Processing split by size&quot;);</span>
    +<span class="nc" id="L88">                        long maxBytes = GeneralUtils.convertSizeToBytes(value);</span>
    +<span class="nc" id="L89">                        log.debug(&quot;Max bytes per document: {}&quot;, maxBytes);</span>
    +<span class="nc" id="L90">                        handleSplitBySize(sourceDocument, maxBytes, zipOut, filename);</span>
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">                    } else if (type == 1) {</span>
    +<span class="nc" id="L92">                        log.debug(&quot;Processing split by page count&quot;);</span>
    +<span class="nc" id="L93">                        int pageCount = Integer.parseInt(value);</span>
    +<span class="nc" id="L94">                        log.debug(&quot;Pages per document: {}&quot;, pageCount);</span>
    +<span class="nc" id="L95">                        handleSplitByPageCount(sourceDocument, pageCount, zipOut, filename);</span>
    +<span class="nc bnc" id="L96" title="All 2 branches missed.">                    } else if (type == 2) {</span>
    +<span class="nc" id="L97">                        log.debug(&quot;Processing split by document count&quot;);</span>
    +<span class="nc" id="L98">                        int documentCount = Integer.parseInt(value);</span>
    +<span class="nc" id="L99">                        log.debug(&quot;Total number of documents: {}&quot;, documentCount);</span>
    +<span class="nc" id="L100">                        handleSplitByDocCount(sourceDocument, documentCount, zipOut, filename);</span>
    +<span class="nc" id="L101">                    } else {</span>
    +<span class="nc" id="L102">                        log.error(&quot;Invalid split type: {}&quot;, type);</span>
    +<span class="nc" id="L103">                        throw new IllegalArgumentException(</span>
    +                                &quot;Invalid argument for split type: &quot; + type);
    +                    }
    +
    +<span class="nc" id="L107">                    log.debug(&quot;PDF splitting completed successfully&quot;);</span>
    +<span class="nc" id="L108">                } catch (Exception e) {</span>
    +<span class="nc" id="L109">                    log.error(&quot;Error loading or processing PDF document&quot;, e);</span>
    +<span class="nc" id="L110">                    throw e;</span>
    +<span class="nc" id="L111">                }</span>
    +<span class="nc" id="L112">            } catch (IOException e) {</span>
    +<span class="nc" id="L113">                log.error(&quot;Error creating or writing to ZIP file&quot;, e);</span>
    +<span class="nc" id="L114">                throw e;</span>
    +<span class="nc" id="L115">            }</span>
    +
    +<span class="nc" id="L117">        } catch (Exception e) {</span>
    +<span class="nc" id="L118">            log.error(&quot;Exception during PDF splitting process&quot;, e);</span>
    +<span class="nc" id="L119">            throw e; // Re-throw to ensure proper error response</span>
    +        } finally {
    +            try {
    +<span class="nc" id="L122">                log.debug(&quot;Reading ZIP file data&quot;);</span>
    +<span class="nc" id="L123">                data = Files.readAllBytes(zipFile);</span>
    +<span class="nc" id="L124">                log.debug(&quot;Successfully read {} bytes from ZIP file&quot;, data.length);</span>
    +<span class="nc" id="L125">            } catch (IOException e) {</span>
    +<span class="nc" id="L126">                log.error(&quot;Error reading ZIP file data&quot;, e);</span>
    +<span class="nc" id="L127">            }</span>
    +
    +            try {
    +<span class="nc" id="L130">                log.debug(&quot;Deleting temporary ZIP file&quot;);</span>
    +<span class="nc" id="L131">                boolean deleted = Files.deleteIfExists(zipFile);</span>
    +<span class="nc" id="L132">                log.debug(&quot;Temporary ZIP file deleted: {}&quot;, deleted);</span>
    +<span class="nc" id="L133">            } catch (IOException e) {</span>
    +<span class="nc" id="L134">                log.error(&quot;Error deleting temporary ZIP file&quot;, e);</span>
    +<span class="nc" id="L135">            }</span>
    +        }
    +
    +<span class="nc bnc" id="L138" title="All 2 branches missed.">        log.debug(&quot;Returning response with {} bytes of data&quot;, data != null ? data.length : 0);</span>
    +<span class="nc" id="L139">        return WebResponseUtils.bytesToWebResponse(</span>
    +                data, filename + &quot;.zip&quot;, MediaType.APPLICATION_OCTET_STREAM);
    +    }
    +
    +    private void handleSplitBySize(
    +            PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
    +            throws IOException {
    +<span class="nc" id="L146">        log.debug(&quot;Starting handleSplitBySize with maxBytes={}&quot;, maxBytes);</span>
    +
    +<span class="nc" id="L148">        PDDocument currentDoc =</span>
    +<span class="nc" id="L149">                pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);</span>
    +<span class="nc" id="L150">        int fileIndex = 1;</span>
    +<span class="nc" id="L151">        int totalPages = sourceDocument.getNumberOfPages();</span>
    +<span class="nc" id="L152">        int pageAdded = 0;</span>
    +
    +        // Smart size check frequency - check more often with larger documents
    +<span class="nc" id="L155">        int baseCheckFrequency = 5;</span>
    +
    +<span class="nc bnc" id="L157" title="All 2 branches missed.">        for (int pageIndex = 0; pageIndex &lt; totalPages; pageIndex++) {</span>
    +<span class="nc" id="L158">            PDPage page = sourceDocument.getPage(pageIndex);</span>
    +<span class="nc" id="L159">            log.debug(&quot;Processing page {} of {}&quot;, pageIndex + 1, totalPages);</span>
    +
    +            // Add the page to current document
    +<span class="nc" id="L162">            PDPage newPage = new PDPage(page.getCOSObject());</span>
    +<span class="nc" id="L163">            currentDoc.addPage(newPage);</span>
    +<span class="nc" id="L164">            pageAdded++;</span>
    +
    +            // Dynamic size checking based on document size and page count
    +<span class="nc bnc" id="L167" title="All 6 branches missed.">            boolean shouldCheckSize =</span>
    +                    (pageAdded % baseCheckFrequency == 0)
    +                            || (pageIndex == totalPages - 1)
    +                            || (pageAdded &gt;= 20); // Always check after 20 pages
    +
    +<span class="nc bnc" id="L172" title="All 2 branches missed.">            if (shouldCheckSize) {</span>
    +<span class="nc" id="L173">                log.debug(&quot;Performing size check after {} pages&quot;, pageAdded);</span>
    +<span class="nc" id="L174">                ByteArrayOutputStream checkSizeStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L175">                currentDoc.save(checkSizeStream);</span>
    +<span class="nc" id="L176">                long actualSize = checkSizeStream.size();</span>
    +<span class="nc" id="L177">                log.debug(&quot;Current document size: {} bytes (max: {} bytes)&quot;, actualSize, maxBytes);</span>
    +
    +<span class="nc bnc" id="L179" title="All 2 branches missed.">                if (actualSize &gt; maxBytes) {</span>
    +                    // We exceeded the limit - remove the last page and save
    +<span class="nc bnc" id="L181" title="All 2 branches missed.">                    if (currentDoc.getNumberOfPages() &gt; 1) {</span>
    +<span class="nc" id="L182">                        currentDoc.removePage(currentDoc.getNumberOfPages() - 1);</span>
    +<span class="nc" id="L183">                        pageIndex--; // Process this page again in the next document</span>
    +<span class="nc" id="L184">                        log.debug(&quot;Size limit exceeded - removed last page&quot;);</span>
    +                    }
    +
    +<span class="nc" id="L187">                    log.debug(</span>
    +                            &quot;Saving document with {} pages as part {}&quot;,
    +<span class="nc" id="L189">                            currentDoc.getNumberOfPages(),</span>
    +<span class="nc" id="L190">                            fileIndex);</span>
    +<span class="nc" id="L191">                    saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);</span>
    +<span class="nc" id="L192">                    currentDoc = new PDDocument();</span>
    +<span class="nc" id="L193">                    pageAdded = 0;</span>
    +<span class="nc bnc" id="L194" title="All 2 branches missed.">                } else if (pageIndex &lt; totalPages - 1) {</span>
    +                    // We're under the limit, calculate if we might fit more pages
    +                    // Try to predict how many more similar pages might fit
    +<span class="nc bnc" id="L197" title="All 4 branches missed.">                    if (actualSize &lt; maxBytes * 0.75 &amp;&amp; pageAdded &gt; 0) {</span>
    +                        // Rather than using a ratio, look ahead to test actual upcoming pages
    +<span class="nc" id="L199">                        int pagesToLookAhead = Math.min(5, totalPages - pageIndex - 1);</span>
    +
    +<span class="nc bnc" id="L201" title="All 2 branches missed.">                        if (pagesToLookAhead &gt; 0) {</span>
    +<span class="nc" id="L202">                            log.debug(</span>
    +                                    &quot;Testing {} upcoming pages for potential addition&quot;,
    +<span class="nc" id="L204">                                    pagesToLookAhead);</span>
    +
    +                            // Create a temp document with current pages + look-ahead pages
    +<span class="nc" id="L207">                            PDDocument testDoc = new PDDocument();</span>
    +                            // First copy existing pages
    +<span class="nc bnc" id="L209" title="All 2 branches missed.">                            for (int i = 0; i &lt; currentDoc.getNumberOfPages(); i++) {</span>
    +<span class="nc" id="L210">                                testDoc.addPage(new PDPage(currentDoc.getPage(i).getCOSObject()));</span>
    +                            }
    +
    +                            // Try adding look-ahead pages one by one
    +<span class="nc" id="L214">                            int extraPagesAdded = 0;</span>
    +<span class="nc bnc" id="L215" title="All 2 branches missed.">                            for (int i = 0; i &lt; pagesToLookAhead; i++) {</span>
    +<span class="nc" id="L216">                                int testPageIndex = pageIndex + 1 + i;</span>
    +<span class="nc" id="L217">                                PDPage testPage = sourceDocument.getPage(testPageIndex);</span>
    +<span class="nc" id="L218">                                testDoc.addPage(new PDPage(testPage.getCOSObject()));</span>
    +
    +                                // Check if we're still under size
    +<span class="nc" id="L221">                                ByteArrayOutputStream testStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L222">                                testDoc.save(testStream);</span>
    +<span class="nc" id="L223">                                long testSize = testStream.size();</span>
    +
    +<span class="nc bnc" id="L225" title="All 2 branches missed.">                                if (testSize &lt;= maxBytes) {</span>
    +<span class="nc" id="L226">                                    extraPagesAdded++;</span>
    +<span class="nc" id="L227">                                    log.debug(</span>
    +                                            &quot;Test: Can add page {} (size would be {})&quot;,
    +<span class="nc" id="L229">                                            testPageIndex + 1,</span>
    +<span class="nc" id="L230">                                            testSize);</span>
    +                                } else {
    +<span class="nc" id="L232">                                    log.debug(</span>
    +                                            &quot;Test: Cannot add page {} (size would be {})&quot;,
    +<span class="nc" id="L234">                                            testPageIndex + 1,</span>
    +<span class="nc" id="L235">                                            testSize);</span>
    +<span class="nc" id="L236">                                    break;</span>
    +                                }
    +                            }
    +
    +<span class="nc" id="L240">                            testDoc.close();</span>
    +
    +                            // Add the pages we verified would fit
    +<span class="nc bnc" id="L243" title="All 2 branches missed.">                            if (extraPagesAdded &gt; 0) {</span>
    +<span class="nc" id="L244">                                log.debug(&quot;Adding {} verified pages ahead&quot;, extraPagesAdded);</span>
    +<span class="nc bnc" id="L245" title="All 2 branches missed.">                                for (int i = 0; i &lt; extraPagesAdded; i++) {</span>
    +<span class="nc" id="L246">                                    int extraPageIndex = pageIndex + 1 + i;</span>
    +<span class="nc" id="L247">                                    PDPage extraPage = sourceDocument.getPage(extraPageIndex);</span>
    +<span class="nc" id="L248">                                    currentDoc.addPage(new PDPage(extraPage.getCOSObject()));</span>
    +                                }
    +<span class="nc" id="L250">                                pageIndex += extraPagesAdded;</span>
    +<span class="nc" id="L251">                                pageAdded += extraPagesAdded;</span>
    +                            }
    +                        }
    +                    }
    +                }
    +            }
    +        }
    +
    +        // Save final document if it has any pages
    +<span class="nc bnc" id="L260" title="All 2 branches missed.">        if (currentDoc.getNumberOfPages() &gt; 0) {</span>
    +<span class="nc" id="L261">            log.debug(</span>
    +                    &quot;Saving final document with {} pages as part {}&quot;,
    +<span class="nc" id="L263">                    currentDoc.getNumberOfPages(),</span>
    +<span class="nc" id="L264">                    fileIndex);</span>
    +<span class="nc" id="L265">            saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);</span>
    +        }
    +
    +<span class="nc" id="L268">        log.debug(&quot;Completed handleSplitBySize with {} document parts created&quot;, fileIndex - 1);</span>
    +<span class="nc" id="L269">    }</span>
    +
    +    private void handleSplitByPageCount(
    +            PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
    +            throws IOException {
    +<span class="nc" id="L274">        log.debug(&quot;Starting handleSplitByPageCount with pageCount={}&quot;, pageCount);</span>
    +<span class="nc" id="L275">        int currentPageCount = 0;</span>
    +<span class="nc" id="L276">        log.debug(&quot;Creating initial output document&quot;);</span>
    +<span class="nc" id="L277">        PDDocument currentDoc = null;</span>
    +        try {
    +<span class="nc" id="L279">            currentDoc = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);</span>
    +<span class="nc" id="L280">            log.debug(&quot;Successfully created initial output document&quot;);</span>
    +<span class="nc" id="L281">        } catch (Exception e) {</span>
    +<span class="nc" id="L282">            log.error(&quot;Error creating initial output document&quot;, e);</span>
    +<span class="nc" id="L283">            throw new IOException(&quot;Failed to create initial output document&quot;, e);</span>
    +<span class="nc" id="L284">        }</span>
    +
    +<span class="nc" id="L286">        int fileIndex = 1;</span>
    +<span class="nc" id="L287">        int pageIndex = 0;</span>
    +<span class="nc" id="L288">        int totalPages = sourceDocument.getNumberOfPages();</span>
    +<span class="nc" id="L289">        log.debug(&quot;Processing {} pages&quot;, totalPages);</span>
    +
    +        try {
    +<span class="nc bnc" id="L292" title="All 2 branches missed.">            for (PDPage page : sourceDocument.getPages()) {</span>
    +<span class="nc" id="L293">                pageIndex++;</span>
    +<span class="nc" id="L294">                log.debug(&quot;Processing page {} of {}&quot;, pageIndex, totalPages);</span>
    +
    +                try {
    +<span class="nc" id="L297">                    log.debug(&quot;Adding page {} to current document&quot;, pageIndex);</span>
    +<span class="nc" id="L298">                    currentDoc.addPage(page);</span>
    +<span class="nc" id="L299">                    log.debug(&quot;Successfully added page {} to current document&quot;, pageIndex);</span>
    +<span class="nc" id="L300">                } catch (Exception e) {</span>
    +<span class="nc" id="L301">                    log.error(&quot;Error adding page {} to current document&quot;, pageIndex, e);</span>
    +<span class="nc" id="L302">                    throw new IOException(&quot;Failed to add page to document&quot;, e);</span>
    +<span class="nc" id="L303">                }</span>
    +
    +<span class="nc" id="L305">                currentPageCount++;</span>
    +<span class="nc" id="L306">                log.debug(&quot;Current page count: {}/{}&quot;, currentPageCount, pageCount);</span>
    +
    +<span class="nc bnc" id="L308" title="All 2 branches missed.">                if (currentPageCount == pageCount) {</span>
    +<span class="nc" id="L309">                    log.debug(</span>
    +                            &quot;Reached target page count ({}), saving current document as part {}&quot;,
    +<span class="nc" id="L311">                            pageCount,</span>
    +<span class="nc" id="L312">                            fileIndex);</span>
    +                    try {
    +<span class="nc" id="L314">                        saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);</span>
    +<span class="nc" id="L315">                        log.debug(&quot;Successfully saved document part {}&quot;, fileIndex - 1);</span>
    +<span class="nc" id="L316">                    } catch (Exception e) {</span>
    +<span class="nc" id="L317">                        log.error(&quot;Error saving document part {}&quot;, fileIndex - 1, e);</span>
    +<span class="nc" id="L318">                        throw e;</span>
    +<span class="nc" id="L319">                    }</span>
    +
    +                    try {
    +<span class="nc" id="L322">                        log.debug(&quot;Creating new document for next part&quot;);</span>
    +<span class="nc" id="L323">                        currentDoc = new PDDocument();</span>
    +<span class="nc" id="L324">                        log.debug(&quot;Successfully created new document&quot;);</span>
    +<span class="nc" id="L325">                    } catch (Exception e) {</span>
    +<span class="nc" id="L326">                        log.error(&quot;Error creating new document for next part&quot;, e);</span>
    +<span class="nc" id="L327">                        throw new IOException(&quot;Failed to create new document&quot;, e);</span>
    +<span class="nc" id="L328">                    }</span>
    +
    +<span class="nc" id="L330">                    currentPageCount = 0;</span>
    +<span class="nc" id="L331">                    log.debug(&quot;Reset current page count to 0&quot;);</span>
    +                }
    +<span class="nc" id="L333">            }</span>
    +<span class="nc" id="L334">        } catch (Exception e) {</span>
    +<span class="nc" id="L335">            log.error(&quot;Error iterating through pages&quot;, e);</span>
    +<span class="nc" id="L336">            throw new IOException(&quot;Failed to iterate through pages&quot;, e);</span>
    +<span class="nc" id="L337">        }</span>
    +
    +        // Add the last document if it contains any pages
    +        try {
    +<span class="nc bnc" id="L341" title="All 2 branches missed.">            if (currentDoc.getPages().getCount() != 0) {</span>
    +<span class="nc" id="L342">                log.debug(</span>
    +                        &quot;Saving final document with {} pages as part {}&quot;,
    +<span class="nc" id="L344">                        currentDoc.getPages().getCount(),</span>
    +<span class="nc" id="L345">                        fileIndex);</span>
    +                try {
    +<span class="nc" id="L347">                    saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);</span>
    +<span class="nc" id="L348">                    log.debug(&quot;Successfully saved final document part {}&quot;, fileIndex - 1);</span>
    +<span class="nc" id="L349">                } catch (Exception e) {</span>
    +<span class="nc" id="L350">                    log.error(&quot;Error saving final document part {}&quot;, fileIndex - 1, e);</span>
    +<span class="nc" id="L351">                    throw e;</span>
    +<span class="nc" id="L352">                }</span>
    +            } else {
    +<span class="nc" id="L354">                log.debug(&quot;Final document has no pages, skipping&quot;);</span>
    +            }
    +<span class="nc" id="L356">        } catch (Exception e) {</span>
    +<span class="nc" id="L357">            log.error(&quot;Error checking or saving final document&quot;, e);</span>
    +<span class="nc" id="L358">            throw new IOException(&quot;Failed to process final document&quot;, e);</span>
    +        } finally {
    +            try {
    +<span class="nc" id="L361">                log.debug(&quot;Closing final document&quot;);</span>
    +<span class="nc" id="L362">                currentDoc.close();</span>
    +<span class="nc" id="L363">                log.debug(&quot;Successfully closed final document&quot;);</span>
    +<span class="nc" id="L364">            } catch (Exception e) {</span>
    +<span class="nc" id="L365">                log.error(&quot;Error closing final document&quot;, e);</span>
    +<span class="nc" id="L366">            }</span>
    +        }
    +
    +<span class="nc" id="L369">        log.debug(&quot;Completed handleSplitByPageCount with {} document parts created&quot;, fileIndex - 1);</span>
    +<span class="nc" id="L370">    }</span>
    +
    +    private void handleSplitByDocCount(
    +            PDDocument sourceDocument,
    +            int documentCount,
    +            ZipOutputStream zipOut,
    +            String baseFilename)
    +            throws IOException {
    +<span class="nc" id="L378">        log.debug(&quot;Starting handleSplitByDocCount with documentCount={}&quot;, documentCount);</span>
    +<span class="nc" id="L379">        int totalPageCount = sourceDocument.getNumberOfPages();</span>
    +<span class="nc" id="L380">        log.debug(&quot;Total pages in source document: {}&quot;, totalPageCount);</span>
    +
    +<span class="nc" id="L382">        int pagesPerDocument = totalPageCount / documentCount;</span>
    +<span class="nc" id="L383">        int extraPages = totalPageCount % documentCount;</span>
    +<span class="nc" id="L384">        log.debug(&quot;Pages per document: {}, Extra pages: {}&quot;, pagesPerDocument, extraPages);</span>
    +
    +<span class="nc" id="L386">        int currentPageIndex = 0;</span>
    +<span class="nc" id="L387">        int fileIndex = 1;</span>
    +
    +<span class="nc bnc" id="L389" title="All 2 branches missed.">        for (int i = 0; i &lt; documentCount; i++) {</span>
    +<span class="nc" id="L390">            log.debug(&quot;Creating document {} of {}&quot;, i + 1, documentCount);</span>
    +<span class="nc" id="L391">            PDDocument currentDoc = null;</span>
    +            try {
    +<span class="nc" id="L393">                currentDoc = pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);</span>
    +<span class="nc" id="L394">                log.debug(&quot;Successfully created document {} of {}&quot;, i + 1, documentCount);</span>
    +<span class="nc" id="L395">            } catch (Exception e) {</span>
    +<span class="nc" id="L396">                log.error(&quot;Error creating document {} of {}&quot;, i + 1, documentCount, e);</span>
    +<span class="nc" id="L397">                throw new IOException(&quot;Failed to create document&quot;, e);</span>
    +<span class="nc" id="L398">            }</span>
    +
    +<span class="nc bnc" id="L400" title="All 2 branches missed.">            int pagesToAdd = pagesPerDocument + (i &lt; extraPages ? 1 : 0);</span>
    +<span class="nc" id="L401">            log.debug(&quot;Adding {} pages to document {}&quot;, pagesToAdd, i + 1);</span>
    +
    +<span class="nc bnc" id="L403" title="All 2 branches missed.">            for (int j = 0; j &lt; pagesToAdd; j++) {</span>
    +                try {
    +<span class="nc" id="L405">                    log.debug(</span>
    +                            &quot;Adding page {} (index {}) to document {}&quot;,
    +<span class="nc" id="L407">                            j + 1,</span>
    +<span class="nc" id="L408">                            currentPageIndex,</span>
    +<span class="nc" id="L409">                            i + 1);</span>
    +<span class="nc" id="L410">                    currentDoc.addPage(sourceDocument.getPage(currentPageIndex));</span>
    +<span class="nc" id="L411">                    log.debug(&quot;Successfully added page {} to document {}&quot;, j + 1, i + 1);</span>
    +<span class="nc" id="L412">                    currentPageIndex++;</span>
    +<span class="nc" id="L413">                } catch (Exception e) {</span>
    +<span class="nc" id="L414">                    log.error(&quot;Error adding page {} to document {}&quot;, j + 1, i + 1, e);</span>
    +<span class="nc" id="L415">                    throw new IOException(&quot;Failed to add page to document&quot;, e);</span>
    +<span class="nc" id="L416">                }</span>
    +            }
    +
    +            try {
    +<span class="nc" id="L420">                log.debug(&quot;Saving document {} with {} pages&quot;, i + 1, pagesToAdd);</span>
    +<span class="nc" id="L421">                saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);</span>
    +<span class="nc" id="L422">                log.debug(&quot;Successfully saved document {}&quot;, i + 1);</span>
    +<span class="nc" id="L423">            } catch (Exception e) {</span>
    +<span class="nc" id="L424">                log.error(&quot;Error saving document {}&quot;, i + 1, e);</span>
    +<span class="nc" id="L425">                throw e;</span>
    +<span class="nc" id="L426">            }</span>
    +        }
    +
    +<span class="nc" id="L429">        log.debug(&quot;Completed handleSplitByDocCount with {} documents created&quot;, documentCount);</span>
    +<span class="nc" id="L430">    }</span>
    +
    +    private void saveDocumentToZip(
    +            PDDocument document, ZipOutputStream zipOut, String baseFilename, int index)
    +            throws IOException {
    +<span class="nc" id="L435">        log.debug(&quot;Starting saveDocumentToZip for document part {}&quot;, index);</span>
    +<span class="nc" id="L436">        ByteArrayOutputStream outStream = new ByteArrayOutputStream();</span>
    +
    +        try {
    +<span class="nc" id="L439">            log.debug(&quot;Saving document part {} to byte array&quot;, index);</span>
    +<span class="nc" id="L440">            document.save(outStream);</span>
    +<span class="nc" id="L441">            log.debug(&quot;Successfully saved document part {} ({} bytes)&quot;, index, outStream.size());</span>
    +<span class="nc" id="L442">        } catch (Exception e) {</span>
    +<span class="nc" id="L443">            log.error(&quot;Error saving document part {} to byte array&quot;, index, e);</span>
    +<span class="nc" id="L444">            throw new IOException(&quot;Failed to save document to byte array&quot;, e);</span>
    +<span class="nc" id="L445">        }</span>
    +
    +        try {
    +<span class="nc" id="L448">            log.debug(&quot;Closing document part {}&quot;, index);</span>
    +<span class="nc" id="L449">            document.close();</span>
    +<span class="nc" id="L450">            log.debug(&quot;Successfully closed document part {}&quot;, index);</span>
    +<span class="nc" id="L451">        } catch (Exception e) {</span>
    +<span class="nc" id="L452">            log.error(&quot;Error closing document part {}&quot;, index, e);</span>
    +            // Continue despite close error
    +<span class="nc" id="L454">        }</span>
    +
    +        try {
    +            // Create a new zip entry
    +<span class="nc" id="L458">            String entryName = baseFilename + &quot;_&quot; + index + &quot;.pdf&quot;;</span>
    +<span class="nc" id="L459">            log.debug(&quot;Creating ZIP entry: {}&quot;, entryName);</span>
    +<span class="nc" id="L460">            ZipEntry zipEntry = new ZipEntry(entryName);</span>
    +<span class="nc" id="L461">            zipOut.putNextEntry(zipEntry);</span>
    +
    +<span class="nc" id="L463">            byte[] bytes = outStream.toByteArray();</span>
    +<span class="nc" id="L464">            log.debug(&quot;Writing {} bytes to ZIP entry&quot;, bytes.length);</span>
    +<span class="nc" id="L465">            zipOut.write(bytes);</span>
    +
    +<span class="nc" id="L467">            log.debug(&quot;Closing ZIP entry&quot;);</span>
    +<span class="nc" id="L468">            zipOut.closeEntry();</span>
    +<span class="nc" id="L469">            log.debug(&quot;Successfully added document part {} to ZIP&quot;, index);</span>
    +<span class="nc" id="L470">        } catch (Exception e) {</span>
    +<span class="nc" id="L471">            log.error(&quot;Error adding document part {} to ZIP&quot;, index, e);</span>
    +<span class="nc" id="L472">            throw new IOException(&quot;Failed to add document to ZIP file&quot;, e);</span>
    +<span class="nc" id="L473">        }</span>
    +<span class="nc" id="L474">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/ToSinglePageController.html b/testResult/html/stirling.software.SPDF.controller.api/ToSinglePageController.html
    new file mode 100644
    index 000000000..2b34141dc
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/ToSinglePageController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ToSinglePageController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">ToSinglePageController</span></div><h1>ToSinglePageController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">147 of 147</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">38</td><td class="ctr2">38</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ToSinglePageController.java.html#L51" class="el_method">pdfToSinglePage(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="141" alt="141"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">35</td><td class="ctr2" id="i0">35</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ToSinglePageController.java.html#L35" class="el_method">ToSinglePageController(CustomPDFDocumentFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/ToSinglePageController.java.html b/testResult/html/stirling.software.SPDF.controller.api/ToSinglePageController.java.html
    new file mode 100644
    index 000000000..9e1e12dbd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/ToSinglePageController.java.html
    @@ -0,0 +1,101 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ToSinglePageController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">ToSinglePageController.java</span></div><h1>ToSinglePageController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.awt.geom.AffineTransform;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +
    +import org.apache.pdfbox.multipdf.LayerUtility;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +import stirling.software.SPDF.utils.WebResponseUtils;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +public class ToSinglePageController {
    +
    +    private final CustomPDFDocumentFactory pdfDocumentFactory;
    +
    +    @Autowired
    +<span class="nc" id="L35">    public ToSinglePageController(CustomPDFDocumentFactory pdfDocumentFactory) {</span>
    +<span class="nc" id="L36">        this.pdfDocumentFactory = pdfDocumentFactory;</span>
    +<span class="nc" id="L37">    }</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf-to-single-page&quot;)
    +    @Operation(
    +            summary = &quot;Convert a multi-page PDF into a single long page PDF&quot;,
    +            description =
    +                    &quot;This endpoint converts a multi-page PDF document into a single paged PDF&quot;
    +                            + &quot; document. The width of the single page will be same as the input's&quot;
    +                            + &quot; width, but the height will be the sum of all the pages' heights.&quot;
    +                            + &quot; Input:PDF Output:PDF Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; pdfToSinglePage(@ModelAttribute PDFFile request)
    +            throws IOException {
    +
    +        // Load the source document
    +<span class="nc" id="L51">        PDDocument sourceDocument = pdfDocumentFactory.load(request);</span>
    +
    +        // Calculate total height and max width
    +<span class="nc" id="L54">        float totalHeight = 0;</span>
    +<span class="nc" id="L55">        float maxWidth = 0;</span>
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">        for (PDPage page : sourceDocument.getPages()) {</span>
    +<span class="nc" id="L57">            PDRectangle pageSize = page.getMediaBox();</span>
    +<span class="nc" id="L58">            totalHeight += pageSize.getHeight();</span>
    +<span class="nc" id="L59">            maxWidth = Math.max(maxWidth, pageSize.getWidth());</span>
    +<span class="nc" id="L60">        }</span>
    +
    +        // Create new document and page with calculated dimensions
    +<span class="nc" id="L63">        PDDocument newDocument =</span>
    +<span class="nc" id="L64">                pdfDocumentFactory.createNewDocumentBasedOnOldDocument(sourceDocument);</span>
    +<span class="nc" id="L65">        PDPage newPage = new PDPage(new PDRectangle(maxWidth, totalHeight));</span>
    +<span class="nc" id="L66">        newDocument.addPage(newPage);</span>
    +
    +        // Initialize the content stream of the new page
    +<span class="nc" id="L69">        PDPageContentStream contentStream = new PDPageContentStream(newDocument, newPage);</span>
    +<span class="nc" id="L70">        contentStream.close();</span>
    +
    +<span class="nc" id="L72">        LayerUtility layerUtility = new LayerUtility(newDocument);</span>
    +<span class="nc" id="L73">        float yOffset = totalHeight;</span>
    +
    +        // For each page, copy its content to the new page at the correct offset
    +<span class="nc bnc" id="L76" title="All 2 branches missed.">        for (PDPage page : sourceDocument.getPages()) {</span>
    +<span class="nc" id="L77">            PDFormXObject form =</span>
    +<span class="nc" id="L78">                    layerUtility.importPageAsForm(</span>
    +<span class="nc" id="L79">                            sourceDocument, sourceDocument.getPages().indexOf(page));</span>
    +<span class="nc" id="L80">            AffineTransform af =</span>
    +<span class="nc" id="L81">                    AffineTransform.getTranslateInstance(</span>
    +<span class="nc" id="L82">                            0, yOffset - page.getMediaBox().getHeight());</span>
    +<span class="nc" id="L83">            layerUtility.wrapInSaveRestore(newPage);</span>
    +<span class="nc" id="L84">            String defaultLayerName = &quot;Layer&quot; + sourceDocument.getPages().indexOf(page);</span>
    +<span class="nc" id="L85">            layerUtility.appendFormAsLayer(newPage, form, af, defaultLayerName);</span>
    +<span class="nc" id="L86">            yOffset -= page.getMediaBox().getHeight();</span>
    +<span class="nc" id="L87">        }</span>
    +
    +<span class="nc" id="L89">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L90">        newDocument.save(baos);</span>
    +<span class="nc" id="L91">        newDocument.close();</span>
    +<span class="nc" id="L92">        sourceDocument.close();</span>
    +
    +<span class="nc" id="L94">        byte[] result = baos.toByteArray();</span>
    +<span class="nc" id="L95">        return WebResponseUtils.bytesToWebResponse(</span>
    +                result,
    +<span class="nc" id="L97">                request.getFileInput().getOriginalFilename().replaceFirst(&quot;[.][^.]+$&quot;, &quot;&quot;)</span>
    +                        + &quot;_singlePage.pdf&quot;);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/UserController.html b/testResult/html/stirling.software.SPDF.controller.api/UserController.html
    new file mode 100644
    index 000000000..17e07a7bc
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/UserController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_class">UserController</span></div><h1>UserController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">756 of 756</td><td class="ctr2">0%</td><td class="bar">98 of 98</td><td class="ctr2">0%</td><td class="ctr1">62</td><td class="ctr2">62</td><td class="ctr1">166</td><td class="ctr2">166</td><td class="ctr1">13</td><td class="ctr2">13</td></tr></tfoot><tbody><tr><td id="a3"><a href="UserController.java.html#L289" class="el_method">changeUserEnabled(String, boolean, Authentication)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="136" alt="136"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">30</td><td class="ctr2" id="i0">30</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a8"><a href="UserController.java.html#L202" class="el_method">saveUser(String, String, String, String, boolean)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="105" height="10" title="120" alt="120"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="98" height="10" title="18" alt="18"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">10</td><td class="ctr2" id="g2">10</td><td class="ctr1" id="h1">25</td><td class="ctr2" id="i1">25</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="UserController.java.html#L88" class="el_method">changeUsername(Principal, String, String, HttpServletRequest, HttpServletResponse, RedirectAttributes)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="97" height="10" title="110" alt="110"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="109" height="10" title="20" alt="20"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">11</td><td class="ctr2" id="g1">11</td><td class="ctr1" id="h2">21</td><td class="ctr2" id="i2">21</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="UserController.java.html#L251" class="el_method">changeRole(String, String, Authentication)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="73" alt="73"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">5</td><td class="ctr2" id="g3">5</td><td class="ctr1" id="h3">17</td><td class="ctr2" id="i3">17</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="UserController.java.html#L131" class="el_method">changePasswordOnLogin(Principal, String, String, HttpServletRequest, HttpServletResponse, RedirectAttributes)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="62" alt="62"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="6" alt="6"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">4</td><td class="ctr2" id="g4">4</td><td class="ctr1" id="h5">12</td><td class="ctr2" id="i5">12</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="UserController.java.html#L336" class="el_method">deleteUser(String, Authentication)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="61" alt="61"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="6" alt="6"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">4</td><td class="ctr2" id="g5">4</td><td class="ctr1" id="h4">13</td><td class="ctr2" id="i4">13</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a0"><a href="UserController.java.html#L159" class="el_method">changePassword(Principal, String, String, HttpServletRequest, HttpServletResponse, RedirectAttributes)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="57" alt="57"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="6" alt="6"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">4</td><td class="ctr2" id="g6">4</td><td class="ctr1" id="h6">11</td><td class="ctr2" id="i6">11</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a11"><a href="UserController.java.html#L180" class="el_method">updateUserSettings(HttpServletRequest, Principal)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="42" alt="42"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h7">8</td><td class="ctr2" id="i7">8</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a10"><a href="UserController.java.html#L373" class="el_method">updateApiKey(Principal)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="28" alt="28"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f7">3</td><td class="ctr2" id="g7">3</td><td class="ctr1" id="h8">8</td><td class="ctr2" id="i8">8</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a7"><a href="UserController.java.html#L66" class="el_method">register(UsernameAndPass, Model)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="26" alt="26"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h9">8</td><td class="ctr2" id="i9">8</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a6"><a href="UserController.java.html#L359" class="el_method">getApiKey(Principal)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="25" alt="25"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="4" alt="4"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f8">3</td><td class="ctr2" id="g8">3</td><td class="ctr1" id="h10">7</td><td class="ctr2" id="i10">7</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a12"><a href="UserController.java.html#L56" class="el_method">UserController(UserService, SessionPersistentRegistry, ApplicationProperties)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="12" alt="12"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">5</td><td class="ctr2" id="i11">5</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a9"><a href="UserController.java.html#L45" class="el_method">static {...}</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h12">1</td><td class="ctr2" id="i12">1</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/UserController.java.html b/testResult/html/stirling.software.SPDF.controller.api/UserController.java.html
    new file mode 100644
    index 000000000..dbbf60bf9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/UserController.java.html
    @@ -0,0 +1,385 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UserController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.api</a> &gt; <span class="el_source">UserController.java</span></div><h1>UserController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.api;
    +
    +import java.io.IOException;
    +import java.security.Principal;
    +import java.sql.SQLException;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Optional;
    +
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.security.access.prepost.PreAuthorize;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.core.session.SessionInformation;
    +import org.springframework.security.core.userdetails.UserDetails;
    +import org.springframework.security.oauth2.core.user.OAuth2User;
    +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
    +import org.springframework.stereotype.Controller;
    +import org.springframework.ui.Model;
    +import org.springframework.web.bind.annotation.*;
    +import org.springframework.web.servlet.mvc.support.RedirectAttributes;
    +import org.springframework.web.servlet.view.RedirectView;
    +
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +import jakarta.servlet.http.HttpServletResponse;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.UserService;
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
    +import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.AuthenticationType;
    +import stirling.software.SPDF.model.Role;
    +import stirling.software.SPDF.model.User;
    +import stirling.software.SPDF.model.api.user.UsernameAndPass;
    +import stirling.software.SPDF.model.exception.UnsupportedProviderException;
    +
    +@Controller
    +@Tag(name = &quot;User&quot;, description = &quot;User APIs&quot;)
    +@RequestMapping(&quot;/api/v1/user&quot;)
    +<span class="nc" id="L45">@Slf4j</span>
    +public class UserController {
    +
    +    private static final String LOGIN_MESSAGETYPE_CREDSUPDATED = &quot;/login?messageType=credsUpdated&quot;;
    +    private final UserService userService;
    +    private final SessionPersistentRegistry sessionRegistry;
    +    private final ApplicationProperties applicationProperties;
    +
    +    public UserController(
    +            UserService userService,
    +            SessionPersistentRegistry sessionRegistry,
    +<span class="nc" id="L56">            ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L57">        this.userService = userService;</span>
    +<span class="nc" id="L58">        this.sessionRegistry = sessionRegistry;</span>
    +<span class="nc" id="L59">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L60">    }</span>
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @PostMapping(&quot;/register&quot;)
    +    public String register(@ModelAttribute UsernameAndPass requestModel, Model model)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">        if (userService.usernameExistsIgnoreCase(requestModel.getUsername())) {</span>
    +<span class="nc" id="L67">            model.addAttribute(&quot;error&quot;, &quot;Username already exists&quot;);</span>
    +<span class="nc" id="L68">            return &quot;register&quot;;</span>
    +        }
    +        try {
    +<span class="nc" id="L71">            userService.saveUser(requestModel.getUsername(), requestModel.getPassword());</span>
    +<span class="nc" id="L72">        } catch (IllegalArgumentException e) {</span>
    +<span class="nc" id="L73">            return &quot;redirect:/login?messageType=invalidUsername&quot;;</span>
    +<span class="nc" id="L74">        }</span>
    +<span class="nc" id="L75">        return &quot;redirect:/login?registered=true&quot;;</span>
    +    }
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @PostMapping(&quot;/change-username&quot;)
    +    public RedirectView changeUsername(
    +            Principal principal,
    +            @RequestParam(name = &quot;currentPasswordChangeUsername&quot;) String currentPassword,
    +            @RequestParam(name = &quot;newUsername&quot;) String newUsername,
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            RedirectAttributes redirectAttributes)
    +            throws IOException, SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L88" title="All 2 branches missed.">        if (!userService.isUsernameValid(newUsername)) {</span>
    +<span class="nc" id="L89">            return new RedirectView(&quot;/account?messageType=invalidUsername&quot;, true);</span>
    +        }
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">        if (principal == null) {</span>
    +<span class="nc" id="L92">            return new RedirectView(&quot;/account?messageType=notAuthenticated&quot;, true);</span>
    +        }
    +        // The username MUST be unique when renaming
    +<span class="nc" id="L95">        Optional&lt;User&gt; userOpt = userService.findByUsername(principal.getName());</span>
    +<span class="nc bnc" id="L96" title="All 4 branches missed.">        if (userOpt == null || userOpt.isEmpty()) {</span>
    +<span class="nc" id="L97">            return new RedirectView(&quot;/account?messageType=userNotFound&quot;, true);</span>
    +        }
    +<span class="nc" id="L99">        User user = userOpt.get();</span>
    +<span class="nc bnc" id="L100" title="All 2 branches missed.">        if (user.getUsername().equals(newUsername)) {</span>
    +<span class="nc" id="L101">            return new RedirectView(&quot;/account?messageType=usernameExists&quot;, true);</span>
    +        }
    +<span class="nc bnc" id="L103" title="All 2 branches missed.">        if (!userService.isPasswordCorrect(user, currentPassword)) {</span>
    +<span class="nc" id="L104">            return new RedirectView(&quot;/account?messageType=incorrectPassword&quot;, true);</span>
    +        }
    +<span class="nc bnc" id="L106" title="All 4 branches missed.">        if (!user.getUsername().equals(newUsername) &amp;&amp; userService.usernameExists(newUsername)) {</span>
    +<span class="nc" id="L107">            return new RedirectView(&quot;/account?messageType=usernameExists&quot;, true);</span>
    +        }
    +<span class="nc bnc" id="L109" title="All 4 branches missed.">        if (newUsername != null &amp;&amp; newUsername.length() &gt; 0) {</span>
    +            try {
    +<span class="nc" id="L111">                userService.changeUsername(user, newUsername);</span>
    +<span class="nc" id="L112">            } catch (IllegalArgumentException e) {</span>
    +<span class="nc" id="L113">                return new RedirectView(&quot;/account?messageType=invalidUsername&quot;, true);</span>
    +<span class="nc" id="L114">            }</span>
    +        }
    +        // Logout using Spring's utility
    +<span class="nc" id="L117">        new SecurityContextLogoutHandler().logout(request, response, null);</span>
    +<span class="nc" id="L118">        return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);</span>
    +    }
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @PostMapping(&quot;/change-password-on-login&quot;)
    +    public RedirectView changePasswordOnLogin(
    +            Principal principal,
    +            @RequestParam(name = &quot;currentPassword&quot;) String currentPassword,
    +            @RequestParam(name = &quot;newPassword&quot;) String newPassword,
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            RedirectAttributes redirectAttributes)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L131" title="All 2 branches missed.">        if (principal == null) {</span>
    +<span class="nc" id="L132">            return new RedirectView(&quot;/change-creds?messageType=notAuthenticated&quot;, true);</span>
    +        }
    +<span class="nc" id="L134">        Optional&lt;User&gt; userOpt = userService.findByUsernameIgnoreCase(principal.getName());</span>
    +<span class="nc bnc" id="L135" title="All 2 branches missed.">        if (userOpt.isEmpty()) {</span>
    +<span class="nc" id="L136">            return new RedirectView(&quot;/change-creds?messageType=userNotFound&quot;, true);</span>
    +        }
    +<span class="nc" id="L138">        User user = userOpt.get();</span>
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">        if (!userService.isPasswordCorrect(user, currentPassword)) {</span>
    +<span class="nc" id="L140">            return new RedirectView(&quot;/change-creds?messageType=incorrectPassword&quot;, true);</span>
    +        }
    +<span class="nc" id="L142">        userService.changePassword(user, newPassword);</span>
    +<span class="nc" id="L143">        userService.changeFirstUse(user, false);</span>
    +        // Logout using Spring's utility
    +<span class="nc" id="L145">        new SecurityContextLogoutHandler().logout(request, response, null);</span>
    +<span class="nc" id="L146">        return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);</span>
    +    }
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @PostMapping(&quot;/change-password&quot;)
    +    public RedirectView changePassword(
    +            Principal principal,
    +            @RequestParam(name = &quot;currentPassword&quot;) String currentPassword,
    +            @RequestParam(name = &quot;newPassword&quot;) String newPassword,
    +            HttpServletRequest request,
    +            HttpServletResponse response,
    +            RedirectAttributes redirectAttributes)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L159" title="All 2 branches missed.">        if (principal == null) {</span>
    +<span class="nc" id="L160">            return new RedirectView(&quot;/account?messageType=notAuthenticated&quot;, true);</span>
    +        }
    +<span class="nc" id="L162">        Optional&lt;User&gt; userOpt = userService.findByUsernameIgnoreCase(principal.getName());</span>
    +<span class="nc bnc" id="L163" title="All 2 branches missed.">        if (userOpt.isEmpty()) {</span>
    +<span class="nc" id="L164">            return new RedirectView(&quot;/account?messageType=userNotFound&quot;, true);</span>
    +        }
    +<span class="nc" id="L166">        User user = userOpt.get();</span>
    +<span class="nc bnc" id="L167" title="All 2 branches missed.">        if (!userService.isPasswordCorrect(user, currentPassword)) {</span>
    +<span class="nc" id="L168">            return new RedirectView(&quot;/account?messageType=incorrectPassword&quot;, true);</span>
    +        }
    +<span class="nc" id="L170">        userService.changePassword(user, newPassword);</span>
    +        // Logout using Spring's utility
    +<span class="nc" id="L172">        new SecurityContextLogoutHandler().logout(request, response, null);</span>
    +<span class="nc" id="L173">        return new RedirectView(LOGIN_MESSAGETYPE_CREDSUPDATED, true);</span>
    +    }
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @PostMapping(&quot;/updateUserSettings&quot;)
    +    public String updateUserSettings(HttpServletRequest request, Principal principal)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L180">        Map&lt;String, String[]&gt; paramMap = request.getParameterMap();</span>
    +<span class="nc" id="L181">        Map&lt;String, String&gt; updates = new HashMap&lt;&gt;();</span>
    +<span class="nc bnc" id="L182" title="All 2 branches missed.">        for (Map.Entry&lt;String, String[]&gt; entry : paramMap.entrySet()) {</span>
    +<span class="nc" id="L183">            updates.put(entry.getKey(), entry.getValue()[0]);</span>
    +<span class="nc" id="L184">        }</span>
    +<span class="nc" id="L185">        log.debug(&quot;Processed updates: {}&quot;, updates);</span>
    +        // Assuming you have a method in userService to update the settings for a user
    +<span class="nc" id="L187">        userService.updateUserSettings(principal.getName(), updates);</span>
    +        // Redirect to a page of your choice after updating
    +<span class="nc" id="L189">        return &quot;redirect:/account&quot;;</span>
    +    }
    +
    +    @PreAuthorize(&quot;hasRole('ROLE_ADMIN')&quot;)
    +    @PostMapping(&quot;/admin/saveUser&quot;)
    +    public RedirectView saveUser(
    +            @RequestParam(name = &quot;username&quot;, required = true) String username,
    +            @RequestParam(name = &quot;password&quot;, required = false) String password,
    +            @RequestParam(name = &quot;role&quot;) String role,
    +            @RequestParam(name = &quot;authType&quot;) String authType,
    +            @RequestParam(name = &quot;forceChange&quot;, required = false, defaultValue = &quot;false&quot;)
    +                    boolean forceChange)
    +            throws IllegalArgumentException, SQLException, UnsupportedProviderException {
    +<span class="nc bnc" id="L202" title="All 2 branches missed.">        if (!userService.isUsernameValid(username)) {</span>
    +<span class="nc" id="L203">            return new RedirectView(&quot;/adminSettings?messageType=invalidUsername&quot;, true);</span>
    +        }
    +<span class="nc bnc" id="L205" title="All 2 branches missed.">        if (applicationProperties.getPremium().isEnabled()</span>
    +<span class="nc" id="L206">                &amp;&amp; applicationProperties.getPremium().getMaxUsers()</span>
    +<span class="nc bnc" id="L207" title="All 2 branches missed.">                        &lt;= userService.getTotalUsersCount()) {</span>
    +<span class="nc" id="L208">            return new RedirectView(&quot;/adminSettings?messageType=maxUsersReached&quot;, true);</span>
    +        }
    +<span class="nc" id="L210">        Optional&lt;User&gt; userOpt = userService.findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L211" title="All 2 branches missed.">        if (userOpt.isPresent()) {</span>
    +<span class="nc" id="L212">            User user = userOpt.get();</span>
    +<span class="nc bnc" id="L213" title="All 2 branches missed.">            if (user.getUsername().equalsIgnoreCase(username)) {</span>
    +<span class="nc" id="L214">                return new RedirectView(&quot;/adminSettings?messageType=usernameExists&quot;, true);</span>
    +            }
    +        }
    +<span class="nc bnc" id="L217" title="All 2 branches missed.">        if (userService.usernameExistsIgnoreCase(username)) {</span>
    +<span class="nc" id="L218">            return new RedirectView(&quot;/adminSettings?messageType=usernameExists&quot;, true);</span>
    +        }
    +        try {
    +            // Validate the role
    +<span class="nc" id="L222">            Role roleEnum = Role.fromString(role);</span>
    +<span class="nc bnc" id="L223" title="All 2 branches missed.">            if (roleEnum == Role.INTERNAL_API_USER) {</span>
    +                // If the role is INTERNAL_API_USER, reject the request
    +<span class="nc" id="L225">                return new RedirectView(&quot;/adminSettings?messageType=invalidRole&quot;, true);</span>
    +            }
    +<span class="nc" id="L227">        } catch (IllegalArgumentException e) {</span>
    +            // If the role ID is not valid, redirect with an error message
    +<span class="nc" id="L229">            return new RedirectView(&quot;/adminSettings?messageType=invalidRole&quot;, true);</span>
    +<span class="nc" id="L230">        }</span>
    +<span class="nc bnc" id="L231" title="All 2 branches missed.">        if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) {</span>
    +<span class="nc" id="L232">            userService.saveUser(username, AuthenticationType.SSO, role);</span>
    +        } else {
    +<span class="nc bnc" id="L234" title="All 2 branches missed.">            if (password.isBlank()) {</span>
    +<span class="nc" id="L235">                return new RedirectView(&quot;/adminSettings?messageType=invalidPassword&quot;, true);</span>
    +            }
    +<span class="nc" id="L237">            userService.saveUser(username, password, role, forceChange);</span>
    +        }
    +<span class="nc" id="L239">        return new RedirectView(</span>
    +                &quot;/adminSettings&quot;, // Redirect to account page after adding the user
    +                true);
    +    }
    +
    +    @PreAuthorize(&quot;hasRole('ROLE_ADMIN')&quot;)
    +    @PostMapping(&quot;/admin/changeRole&quot;)
    +    public RedirectView changeRole(
    +            @RequestParam(name = &quot;username&quot;) String username,
    +            @RequestParam(name = &quot;role&quot;) String role,
    +            Authentication authentication)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L251">        Optional&lt;User&gt; userOpt = userService.findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L252" title="All 2 branches missed.">        if (!userOpt.isPresent()) {</span>
    +<span class="nc" id="L253">            return new RedirectView(&quot;/adminSettings?messageType=userNotFound&quot;, true);</span>
    +        }
    +<span class="nc bnc" id="L255" title="All 2 branches missed.">        if (!userService.usernameExistsIgnoreCase(username)) {</span>
    +<span class="nc" id="L256">            return new RedirectView(&quot;/adminSettings?messageType=userNotFound&quot;, true);</span>
    +        }
    +        // Get the currently authenticated username
    +<span class="nc" id="L259">        String currentUsername = authentication.getName();</span>
    +        // Check if the provided username matches the current session's username
    +<span class="nc bnc" id="L261" title="All 2 branches missed.">        if (currentUsername.equalsIgnoreCase(username)) {</span>
    +<span class="nc" id="L262">            return new RedirectView(&quot;/adminSettings?messageType=downgradeCurrentUser&quot;, true);</span>
    +        }
    +        try {
    +            // Validate the role
    +<span class="nc" id="L266">            Role roleEnum = Role.fromString(role);</span>
    +<span class="nc bnc" id="L267" title="All 2 branches missed.">            if (roleEnum == Role.INTERNAL_API_USER) {</span>
    +                // If the role is INTERNAL_API_USER, reject the request
    +<span class="nc" id="L269">                return new RedirectView(&quot;/adminSettings?messageType=invalidRole&quot;, true);</span>
    +            }
    +<span class="nc" id="L271">        } catch (IllegalArgumentException e) {</span>
    +            // If the role ID is not valid, redirect with an error message
    +<span class="nc" id="L273">            return new RedirectView(&quot;/adminSettings?messageType=invalidRole&quot;, true);</span>
    +<span class="nc" id="L274">        }</span>
    +<span class="nc" id="L275">        User user = userOpt.get();</span>
    +<span class="nc" id="L276">        userService.changeRole(user, role);</span>
    +<span class="nc" id="L277">        return new RedirectView(</span>
    +                &quot;/adminSettings&quot;, // Redirect to account page after adding the user
    +                true);
    +    }
    +
    +    @PreAuthorize(&quot;hasRole('ROLE_ADMIN')&quot;)
    +    @PostMapping(&quot;/admin/changeUserEnabled/{username}&quot;)
    +    public RedirectView changeUserEnabled(
    +            @PathVariable(&quot;username&quot;) String username,
    +            @RequestParam(&quot;enabled&quot;) boolean enabled,
    +            Authentication authentication)
    +            throws SQLException, UnsupportedProviderException {
    +<span class="nc" id="L289">        Optional&lt;User&gt; userOpt = userService.findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L290" title="All 2 branches missed.">        if (userOpt.isEmpty()) {</span>
    +<span class="nc" id="L291">            return new RedirectView(&quot;/adminSettings?messageType=userNotFound&quot;, true);</span>
    +        }
    +<span class="nc bnc" id="L293" title="All 2 branches missed.">        if (!userService.usernameExistsIgnoreCase(username)) {</span>
    +<span class="nc" id="L294">            return new RedirectView(&quot;/adminSettings?messageType=userNotFound&quot;, true);</span>
    +        }
    +        // Get the currently authenticated username
    +<span class="nc" id="L297">        String currentUsername = authentication.getName();</span>
    +        // Check if the provided username matches the current session's username
    +<span class="nc bnc" id="L299" title="All 2 branches missed.">        if (currentUsername.equalsIgnoreCase(username)) {</span>
    +<span class="nc" id="L300">            return new RedirectView(&quot;/adminSettings?messageType=disabledCurrentUser&quot;, true);</span>
    +        }
    +<span class="nc" id="L302">        User user = userOpt.get();</span>
    +<span class="nc" id="L303">        userService.changeUserEnabled(user, enabled);</span>
    +<span class="nc bnc" id="L304" title="All 2 branches missed.">        if (!enabled) {</span>
    +            // Invalidate all sessions if the user is being disabled
    +<span class="nc" id="L306">            List&lt;Object&gt; principals = sessionRegistry.getAllPrincipals();</span>
    +<span class="nc" id="L307">            String userNameP = &quot;&quot;;</span>
    +<span class="nc bnc" id="L308" title="All 2 branches missed.">            for (Object principal : principals) {</span>
    +<span class="nc" id="L309">                List&lt;SessionInformation&gt; sessionsInformation =</span>
    +<span class="nc" id="L310">                        sessionRegistry.getAllSessions(principal, false);</span>
    +<span class="nc bnc" id="L311" title="All 2 branches missed.">                if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L312">                    userNameP = detailsUser.getUsername();</span>
    +<span class="nc bnc" id="L313" title="All 2 branches missed.">                } else if (principal instanceof OAuth2User oAuth2User) {</span>
    +<span class="nc" id="L314">                    userNameP = oAuth2User.getName();</span>
    +<span class="nc bnc" id="L315" title="All 2 branches missed.">                } else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {</span>
    +<span class="nc" id="L316">                    userNameP = saml2User.name();</span>
    +<span class="nc bnc" id="L317" title="All 2 branches missed.">                } else if (principal instanceof String stringUser) {</span>
    +<span class="nc" id="L318">                    userNameP = stringUser;</span>
    +                }
    +<span class="nc bnc" id="L320" title="All 2 branches missed.">                if (userNameP.equalsIgnoreCase(username)) {</span>
    +<span class="nc bnc" id="L321" title="All 2 branches missed.">                    for (SessionInformation sessionInfo : sessionsInformation) {</span>
    +<span class="nc" id="L322">                        sessionRegistry.expireSession(sessionInfo.getSessionId());</span>
    +<span class="nc" id="L323">                    }</span>
    +                }
    +<span class="nc" id="L325">            }</span>
    +        }
    +<span class="nc" id="L327">        return new RedirectView(</span>
    +                &quot;/adminSettings&quot;, // Redirect to account page after adding the user
    +                true);
    +    }
    +
    +    @PreAuthorize(&quot;hasRole('ROLE_ADMIN')&quot;)
    +    @PostMapping(&quot;/admin/deleteUser/{username}&quot;)
    +    public RedirectView deleteUser(
    +            @PathVariable(&quot;username&quot;) String username, Authentication authentication) {
    +<span class="nc bnc" id="L336" title="All 2 branches missed.">        if (!userService.usernameExistsIgnoreCase(username)) {</span>
    +<span class="nc" id="L337">            return new RedirectView(&quot;/adminSettings?messageType=deleteUsernameExists&quot;, true);</span>
    +        }
    +        // Get the currently authenticated username
    +<span class="nc" id="L340">        String currentUsername = authentication.getName();</span>
    +        // Check if the provided username matches the current session's username
    +<span class="nc bnc" id="L342" title="All 2 branches missed.">        if (currentUsername.equalsIgnoreCase(username)) {</span>
    +<span class="nc" id="L343">            return new RedirectView(&quot;/adminSettings?messageType=deleteCurrentUser&quot;, true);</span>
    +        }
    +        // Invalidate all sessions before deleting the user
    +<span class="nc" id="L346">        List&lt;SessionInformation&gt; sessionsInformations =</span>
    +<span class="nc" id="L347">                sessionRegistry.getAllSessions(username, false);</span>
    +<span class="nc bnc" id="L348" title="All 2 branches missed.">        for (SessionInformation sessionsInformation : sessionsInformations) {</span>
    +<span class="nc" id="L349">            sessionRegistry.expireSession(sessionsInformation.getSessionId());</span>
    +<span class="nc" id="L350">            sessionRegistry.removeSessionInformation(sessionsInformation.getSessionId());</span>
    +<span class="nc" id="L351">        }</span>
    +<span class="nc" id="L352">        userService.deleteUser(username);</span>
    +<span class="nc" id="L353">        return new RedirectView(&quot;/adminSettings&quot;, true);</span>
    +    }
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @PostMapping(&quot;/get-api-key&quot;)
    +    public ResponseEntity&lt;String&gt; getApiKey(Principal principal) {
    +<span class="nc bnc" id="L359" title="All 2 branches missed.">        if (principal == null) {</span>
    +<span class="nc" id="L360">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;User not authenticated.&quot;);</span>
    +        }
    +<span class="nc" id="L362">        String username = principal.getName();</span>
    +<span class="nc" id="L363">        String apiKey = userService.getApiKeyForUser(username);</span>
    +<span class="nc bnc" id="L364" title="All 2 branches missed.">        if (apiKey == null) {</span>
    +<span class="nc" id="L365">            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(&quot;API key not found for user.&quot;);</span>
    +        }
    +<span class="nc" id="L367">        return ResponseEntity.ok(apiKey);</span>
    +    }
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @PostMapping(&quot;/update-api-key&quot;)
    +    public ResponseEntity&lt;String&gt; updateApiKey(Principal principal) {
    +<span class="nc bnc" id="L373" title="All 2 branches missed.">        if (principal == null) {</span>
    +<span class="nc" id="L374">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;User not authenticated.&quot;);</span>
    +        }
    +<span class="nc" id="L376">        String username = principal.getName();</span>
    +<span class="nc" id="L377">        User user = userService.refreshApiKeyForUser(username);</span>
    +<span class="nc" id="L378">        String apiKey = user.getApiKey();</span>
    +<span class="nc bnc" id="L379" title="All 2 branches missed.">        if (apiKey == null) {</span>
    +<span class="nc" id="L380">            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(&quot;API key not found for user.&quot;);</span>
    +        }
    +<span class="nc" id="L382">        return ResponseEntity.ok(apiKey);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/index.html b/testResult/html/stirling.software.SPDF.controller.api/index.html
    new file mode 100644
    index 000000000..a69bc391d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api</span></div><h1>stirling.software.SPDF.controller.api</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">5,976 of 5,976</td><td class="ctr2">0%</td><td class="bar">454 of 454</td><td class="ctr2">0%</td><td class="ctr1">337</td><td class="ctr2">337</td><td class="ctr1">1,435</td><td class="ctr2">1,435</td><td class="ctr1">103</td><td class="ctr2">103</td><td class="ctr1">19</td><td class="ctr2">19</td></tr></tfoot><tbody><tr><td id="a15"><a href="SplitPdfBySizeController.html" class="el_class">SplitPdfBySizeController</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,142" alt="1,142"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="54" alt="54"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f2">34</td><td class="ctr2" id="g2">34</td><td class="ctr1" id="h0">284</td><td class="ctr2" id="i0">284</td><td class="ctr1" id="j5">7</td><td class="ctr2" id="k5">7</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a18"><a href="UserController.html" class="el_class">UserController</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="79" height="10" title="756" alt="756"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="98" alt="98"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">62</td><td class="ctr2" id="g0">62</td><td class="ctr1" id="h1">166</td><td class="ctr2" id="i1">166</td><td class="ctr1" id="j1">13</td><td class="ctr2" id="k1">13</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a9"><a href="RearrangePagesPDFController.html" class="el_class">RearrangePagesPDFController</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="63" height="10" title="601" alt="601"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="82" height="10" title="67" alt="67"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">53</td><td class="ctr2" id="g1">53</td><td class="ctr1" id="h3">126</td><td class="ctr2" id="i3">126</td><td class="ctr1" id="j0">15</td><td class="ctr2" id="k0">15</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a13"><a href="SplitPdfByChaptersController.html" class="el_class">SplitPdfByChaptersController</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="59" height="10" title="562" alt="562"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="54" alt="54"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">34</td><td class="ctr2" id="g3">34</td><td class="ctr1" id="h2">148</td><td class="ctr2" id="i2">148</td><td class="ctr1" id="j6">7</td><td class="ctr2" id="k6">7</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a1"><a href="AnalysisController.html" class="el_class">AnalysisController</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="442" alt="442"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="14" alt="14"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f7">16</td><td class="ctr2" id="g7">16</td><td class="ctr1" id="h5">86</td><td class="ctr2" id="i5">86</td><td class="ctr1" id="j3">9</td><td class="ctr2" id="k3">9</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a8"><a href="PdfOverlayController.html" class="el_class">PdfOverlayController</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="353" alt="353"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="44" height="10" title="36" alt="36"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">26</td><td class="ctr2" id="g4">26</td><td class="ctr1" id="h6">84</td><td class="ctr2" id="i6">84</td><td class="ctr1" id="j7">7</td><td class="ctr2" id="k7">7</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a5"><a href="MergeController.html" class="el_class">MergeController</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="311" alt="311"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="25" alt="25"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">24</td><td class="ctr2" id="g5">24</td><td class="ctr1" id="h4">89</td><td class="ctr2" id="i4">89</td><td class="ctr1" id="j2">10</td><td class="ctr2" id="k2">10</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a14"><a href="SplitPdfBySectionsController.html" class="el_class">SplitPdfBySectionsController</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="308" alt="308"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="14" alt="14"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f10">10</td><td class="ctr2" id="g10">10</td><td class="ctr1" id="h7">70</td><td class="ctr2" id="i7">70</td><td class="ctr1" id="j9">3</td><td class="ctr2" id="k9">3</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a6"><a href="MultiPageLayoutController.html" class="el_class">MultiPageLayoutController</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="295" alt="295"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="22" alt="22"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">13</td><td class="ctr2" id="g8">13</td><td class="ctr1" id="h9">63</td><td class="ctr2" id="i9">63</td><td class="ctr1" id="j12">2</td><td class="ctr2" id="k12">2</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a16"><a href="SplitPDFController.html" class="el_class">SplitPDFController</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="250" alt="250"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="16" alt="16"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f9">11</td><td class="ctr2" id="g9">11</td><td class="ctr1" id="h8">69</td><td class="ctr2" id="i8">69</td><td class="ctr1" id="j10">3</td><td class="ctr2" id="k10">3</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a11"><a href="ScalePagesController.html" class="el_class">ScalePagesController</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="239" alt="239"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="8" alt="8"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f11">8</td><td class="ctr2" id="g11">8</td><td class="ctr1" id="h11">58</td><td class="ctr2" id="i11">58</td><td class="ctr1" id="j8">4</td><td class="ctr2" id="k8">4</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a3"><a href="DatabaseController.html" class="el_class">DatabaseController</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="229" alt="229"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="24" alt="24"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f6">20</td><td class="ctr2" id="g6">20</td><td class="ctr1" id="h10">62</td><td class="ctr2" id="i10">62</td><td class="ctr1" id="j4">8</td><td class="ctr2" id="k4">8</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a17"><a href="ToSinglePageController.html" class="el_class">ToSinglePageController</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="147" alt="147"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f14">4</td><td class="ctr2" id="g14">4</td><td class="ctr1" id="h12">38</td><td class="ctr2" id="i12">38</td><td class="ctr1" id="j13">2</td><td class="ctr2" id="k13">2</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a2"><a href="CropController.html" class="el_class">CropController</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="116" alt="116"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f16">3</td><td class="ctr2" id="g16">3</td><td class="ctr1" id="h13">29</td><td class="ctr2" id="i13">29</td><td class="ctr1" id="j14">2</td><td class="ctr2" id="k14">2</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a0"><a href="AdditionalLanguageJsController.html" class="el_class">AdditionalLanguageJsController</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="72" alt="72"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f12">5</td><td class="ctr2" id="g12">5</td><td class="ctr1" id="h14">18</td><td class="ctr2" id="i14">18</td><td class="ctr1" id="j11">3</td><td class="ctr2" id="k11">3</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a10"><a href="RotationController.html" class="el_class">RotationController</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="58" alt="58"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f15">4</td><td class="ctr2" id="g15">4</td><td class="ctr1" id="h15">15</td><td class="ctr2" id="i15">15</td><td class="ctr1" id="j15">2</td><td class="ctr2" id="k15">2</td><td class="ctr1" id="l15">1</td><td class="ctr2" id="m15">1</td></tr><tr><td id="a7"><a href="PdfImageRemovalController.html" class="el_class">PdfImageRemovalController</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="41" alt="41"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">2</td><td class="ctr2" id="g18">2</td><td class="ctr1" id="h16">12</td><td class="ctr2" id="i16">12</td><td class="ctr1" id="j16">2</td><td class="ctr2" id="k16">2</td><td class="ctr1" id="l16">1</td><td class="ctr2" id="m16">1</td></tr><tr><td id="a12"><a href="SettingsController.html" class="el_class">SettingsController</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="28" alt="28"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f17">3</td><td class="ctr2" id="g17">3</td><td class="ctr1" id="h17">10</td><td class="ctr2" id="i17">10</td><td class="ctr1" id="j17">2</td><td class="ctr2" id="k17">2</td><td class="ctr1" id="l17">1</td><td class="ctr2" id="m17">1</td></tr><tr><td id="a4"><a href="H2SQLCondition.html" class="el_class">H2SQLCondition</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="26" alt="26"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="6" alt="6"/></td><td class="ctr2" id="e17">0%</td><td class="ctr1" id="f13">5</td><td class="ctr2" id="g13">5</td><td class="ctr1" id="h18">8</td><td class="ctr2" id="i18">8</td><td class="ctr1" id="j18">2</td><td class="ctr2" id="k18">2</td><td class="ctr1" id="l18">1</td><td class="ctr2" id="m18">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.api/index.source.html b/testResult/html/stirling.software.SPDF.controller.api/index.source.html
    new file mode 100644
    index 000000000..d3c5d3da5
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.api/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.api</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.api</span></div><h1>stirling.software.SPDF.controller.api</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">5,976 of 5,976</td><td class="ctr2">0%</td><td class="bar">454 of 454</td><td class="ctr2">0%</td><td class="ctr1">337</td><td class="ctr2">337</td><td class="ctr1">1,435</td><td class="ctr2">1,435</td><td class="ctr1">103</td><td class="ctr2">103</td><td class="ctr1">19</td><td class="ctr2">19</td></tr></tfoot><tbody><tr><td id="a15"><a href="SplitPdfBySizeController.java.html" class="el_source">SplitPdfBySizeController.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,142" alt="1,142"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="54" alt="54"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f2">34</td><td class="ctr2" id="g2">34</td><td class="ctr1" id="h0">284</td><td class="ctr2" id="i0">284</td><td class="ctr1" id="j5">7</td><td class="ctr2" id="k5">7</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a18"><a href="UserController.java.html" class="el_source">UserController.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="79" height="10" title="756" alt="756"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="98" alt="98"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">62</td><td class="ctr2" id="g0">62</td><td class="ctr1" id="h1">166</td><td class="ctr2" id="i1">166</td><td class="ctr1" id="j1">13</td><td class="ctr2" id="k1">13</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a9"><a href="RearrangePagesPDFController.java.html" class="el_source">RearrangePagesPDFController.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="63" height="10" title="601" alt="601"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="82" height="10" title="67" alt="67"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">53</td><td class="ctr2" id="g1">53</td><td class="ctr1" id="h3">126</td><td class="ctr2" id="i3">126</td><td class="ctr1" id="j0">15</td><td class="ctr2" id="k0">15</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a13"><a href="SplitPdfByChaptersController.java.html" class="el_source">SplitPdfByChaptersController.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="59" height="10" title="562" alt="562"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="54" alt="54"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">34</td><td class="ctr2" id="g3">34</td><td class="ctr1" id="h2">148</td><td class="ctr2" id="i2">148</td><td class="ctr1" id="j6">7</td><td class="ctr2" id="k6">7</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a1"><a href="AnalysisController.java.html" class="el_source">AnalysisController.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="442" alt="442"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="14" alt="14"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f7">16</td><td class="ctr2" id="g7">16</td><td class="ctr1" id="h5">86</td><td class="ctr2" id="i5">86</td><td class="ctr1" id="j3">9</td><td class="ctr2" id="k3">9</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a8"><a href="PdfOverlayController.java.html" class="el_source">PdfOverlayController.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="353" alt="353"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="44" height="10" title="36" alt="36"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">26</td><td class="ctr2" id="g4">26</td><td class="ctr1" id="h6">84</td><td class="ctr2" id="i6">84</td><td class="ctr1" id="j7">7</td><td class="ctr2" id="k7">7</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a5"><a href="MergeController.java.html" class="el_source">MergeController.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="311" alt="311"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="25" alt="25"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">24</td><td class="ctr2" id="g5">24</td><td class="ctr1" id="h4">89</td><td class="ctr2" id="i4">89</td><td class="ctr1" id="j2">10</td><td class="ctr2" id="k2">10</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a14"><a href="SplitPdfBySectionsController.java.html" class="el_source">SplitPdfBySectionsController.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="308" alt="308"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="14" alt="14"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f10">10</td><td class="ctr2" id="g10">10</td><td class="ctr1" id="h7">70</td><td class="ctr2" id="i7">70</td><td class="ctr1" id="j9">3</td><td class="ctr2" id="k9">3</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a6"><a href="MultiPageLayoutController.java.html" class="el_source">MultiPageLayoutController.java</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="295" alt="295"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="22" alt="22"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">13</td><td class="ctr2" id="g8">13</td><td class="ctr1" id="h9">63</td><td class="ctr2" id="i9">63</td><td class="ctr1" id="j12">2</td><td class="ctr2" id="k12">2</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a16"><a href="SplitPDFController.java.html" class="el_source">SplitPDFController.java</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="250" alt="250"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="16" alt="16"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f9">11</td><td class="ctr2" id="g9">11</td><td class="ctr1" id="h8">69</td><td class="ctr2" id="i8">69</td><td class="ctr1" id="j10">3</td><td class="ctr2" id="k10">3</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a11"><a href="ScalePagesController.java.html" class="el_source">ScalePagesController.java</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="239" alt="239"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="8" alt="8"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f11">8</td><td class="ctr2" id="g11">8</td><td class="ctr1" id="h11">58</td><td class="ctr2" id="i11">58</td><td class="ctr1" id="j8">4</td><td class="ctr2" id="k8">4</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a3"><a href="DatabaseController.java.html" class="el_source">DatabaseController.java</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="229" alt="229"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="24" alt="24"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f6">20</td><td class="ctr2" id="g6">20</td><td class="ctr1" id="h10">62</td><td class="ctr2" id="i10">62</td><td class="ctr1" id="j4">8</td><td class="ctr2" id="k4">8</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a17"><a href="ToSinglePageController.java.html" class="el_source">ToSinglePageController.java</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="147" alt="147"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f14">4</td><td class="ctr2" id="g14">4</td><td class="ctr1" id="h12">38</td><td class="ctr2" id="i12">38</td><td class="ctr1" id="j13">2</td><td class="ctr2" id="k13">2</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a2"><a href="CropController.java.html" class="el_source">CropController.java</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="116" alt="116"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f16">3</td><td class="ctr2" id="g16">3</td><td class="ctr1" id="h13">29</td><td class="ctr2" id="i13">29</td><td class="ctr1" id="j14">2</td><td class="ctr2" id="k14">2</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a0"><a href="AdditionalLanguageJsController.java.html" class="el_source">AdditionalLanguageJsController.java</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="72" alt="72"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f12">5</td><td class="ctr2" id="g12">5</td><td class="ctr1" id="h14">18</td><td class="ctr2" id="i14">18</td><td class="ctr1" id="j11">3</td><td class="ctr2" id="k11">3</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a10"><a href="RotationController.java.html" class="el_source">RotationController.java</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="58" alt="58"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f15">4</td><td class="ctr2" id="g15">4</td><td class="ctr1" id="h15">15</td><td class="ctr2" id="i15">15</td><td class="ctr1" id="j15">2</td><td class="ctr2" id="k15">2</td><td class="ctr1" id="l15">1</td><td class="ctr2" id="m15">1</td></tr><tr><td id="a7"><a href="PdfImageRemovalController.java.html" class="el_source">PdfImageRemovalController.java</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="41" alt="41"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">2</td><td class="ctr2" id="g18">2</td><td class="ctr1" id="h16">12</td><td class="ctr2" id="i16">12</td><td class="ctr1" id="j16">2</td><td class="ctr2" id="k16">2</td><td class="ctr1" id="l16">1</td><td class="ctr2" id="m16">1</td></tr><tr><td id="a12"><a href="SettingsController.java.html" class="el_source">SettingsController.java</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="28" alt="28"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f17">3</td><td class="ctr2" id="g17">3</td><td class="ctr1" id="h17">10</td><td class="ctr2" id="i17">10</td><td class="ctr1" id="j17">2</td><td class="ctr2" id="k17">2</td><td class="ctr1" id="l17">1</td><td class="ctr2" id="m17">1</td></tr><tr><td id="a4"><a href="H2SQLCondition.java.html" class="el_source">H2SQLCondition.java</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="26" alt="26"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="6" alt="6"/></td><td class="ctr2" id="e17">0%</td><td class="ctr1" id="f13">5</td><td class="ctr2" id="g13">5</td><td class="ctr1" id="h18">8</td><td class="ctr2" id="i18">8</td><td class="ctr1" id="j18">2</td><td class="ctr2" id="k18">2</td><td class="ctr1" id="l18">1</td><td class="ctr2" id="m18">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/AccountWebController.html b/testResult/html/stirling.software.SPDF.controller.web/AccountWebController.html
    new file mode 100644
    index 000000000..d83d8248b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/AccountWebController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AccountWebController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">AccountWebController</span></div><h1>AccountWebController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">874 of 874</td><td class="ctr2">0%</td><td class="bar">145 of 145</td><td class="ctr2">0%</td><td class="ctr1">96</td><td class="ctr2">96</td><td class="ctr1">240</td><td class="ctr2">240</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a5"><a href="AccountWebController.java.html#L75" class="el_method">login(HttpServletRequest, Model, Authentication)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="285" alt="285"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="57" alt="57"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">38</td><td class="ctr2" id="g0">38</td><td class="ctr1" id="h0">76</td><td class="ctr2" id="i0">76</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a6"><a href="AccountWebController.java.html#L213" class="el_method">showAddUserForm(HttpServletRequest, Model, Authentication)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="113" height="10" title="269" alt="269"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="69" height="10" title="33" alt="33"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">20</td><td class="ctr2" id="g1">20</td><td class="ctr1" id="h1">75</td><td class="ctr2" id="i1">75</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="AccountWebController.java.html#L341" class="el_method">account(HttpServletRequest, Model, Authentication)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="67" height="10" title="161" alt="161"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="24" alt="24"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">15</td><td class="ctr2" id="g2">15</td><td class="ctr1" id="h2">41</td><td class="ctr2" id="i2">41</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="AccountWebController.java.html#L407" class="el_method">changeCreds(HttpServletRequest, Model, Authentication)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="73" alt="73"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="17" alt="17"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">11</td><td class="ctr2" id="g3">11</td><td class="ctr1" id="h3">25</td><td class="ctr2" id="i3">25</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="AccountWebController.java.html#L272" class="el_method">lambda$showAddUserForm$1(Map, Map, User, User)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="50" alt="50"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="8" alt="8"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">5</td><td class="ctr2" id="g4">5</td><td class="ctr1" id="h4">13</td><td class="ctr2" id="i4">13</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="AccountWebController.java.html#L65" class="el_method">AccountWebController(ApplicationProperties, SessionPersistentRegistry, UserRepository, boolean)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="15" alt="15"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a3"><a href="AccountWebController.java.html#L138" class="el_method">lambda$login$0(Map.Entry)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="10" alt="10"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a7"><a href="AccountWebController.java.html#L203" class="el_method">showUsage()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="AccountWebController.java.html#L49" class="el_method">static {...}</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/AccountWebController.java.html b/testResult/html/stirling.software.SPDF.controller.web/AccountWebController.java.html
    new file mode 100644
    index 000000000..55cab9161
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/AccountWebController.java.html
    @@ -0,0 +1,449 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AccountWebController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">AccountWebController.java</span></div><h1>AccountWebController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import static stirling.software.SPDF.utils.validation.Validator.validateProvider;
    +
    +import java.time.Instant;
    +import java.time.temporal.ChronoUnit;
    +import java.util.Date;
    +import java.util.HashMap;
    +import java.util.Iterator;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Optional;
    +
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.security.access.prepost.PreAuthorize;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.security.core.userdetails.UserDetails;
    +import org.springframework.security.oauth2.core.user.OAuth2User;
    +import org.springframework.stereotype.Controller;
    +import org.springframework.ui.Model;
    +import org.springframework.web.bind.annotation.GetMapping;
    +
    +import com.fasterxml.jackson.core.JsonProcessingException;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
    +import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.ApplicationProperties.Security;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
    +import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
    +import stirling.software.SPDF.model.Authority;
    +import stirling.software.SPDF.model.Role;
    +import stirling.software.SPDF.model.SessionEntity;
    +import stirling.software.SPDF.model.User;
    +import stirling.software.SPDF.model.provider.GitHubProvider;
    +import stirling.software.SPDF.model.provider.GoogleProvider;
    +import stirling.software.SPDF.model.provider.KeycloakProvider;
    +import stirling.software.SPDF.repository.UserRepository;
    +
    +@Controller
    +<span class="nc" id="L49">@Slf4j</span>
    +@Tag(name = &quot;Account Security&quot;, description = &quot;Account Security APIs&quot;)
    +public class AccountWebController {
    +
    +    public static final String OAUTH_2_AUTHORIZATION = &quot;/oauth2/authorization/&quot;;
    +
    +    private final ApplicationProperties applicationProperties;
    +    private final SessionPersistentRegistry sessionPersistentRegistry;
    +    // Assuming you have a repository for user operations
    +    private final UserRepository userRepository;
    +    private final boolean runningEE;
    +
    +    public AccountWebController(
    +            ApplicationProperties applicationProperties,
    +            SessionPersistentRegistry sessionPersistentRegistry,
    +            UserRepository userRepository,
    +<span class="nc" id="L65">            @Qualifier(&quot;runningEE&quot;) boolean runningEE) {</span>
    +<span class="nc" id="L66">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L67">        this.sessionPersistentRegistry = sessionPersistentRegistry;</span>
    +<span class="nc" id="L68">        this.userRepository = userRepository;</span>
    +<span class="nc" id="L69">        this.runningEE = runningEE;</span>
    +<span class="nc" id="L70">    }</span>
    +
    +    @GetMapping(&quot;/login&quot;)
    +    public String login(HttpServletRequest request, Model model, Authentication authentication) {
    +        // If the user is already authenticated, redirect them to the home page.
    +<span class="nc bnc" id="L75" title="All 4 branches missed.">        if (authentication != null &amp;&amp; authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L76">            return &quot;redirect:/&quot;;</span>
    +        }
    +
    +<span class="nc" id="L79">        Map&lt;String, String&gt; providerList = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L80">        Security securityProps = applicationProperties.getSecurity();</span>
    +<span class="nc" id="L81">        OAUTH2 oauth = securityProps.getOauth2();</span>
    +
    +<span class="nc bnc" id="L83" title="All 2 branches missed.">        if (oauth != null) {</span>
    +<span class="nc bnc" id="L84" title="All 2 branches missed.">            if (oauth.getEnabled()) {</span>
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">                if (oauth.isSettingsValid()) {</span>
    +<span class="nc" id="L86">                    String firstChar = String.valueOf(oauth.getProvider().charAt(0));</span>
    +<span class="nc" id="L87">                    String clientName =</span>
    +<span class="nc" id="L88">                            oauth.getProvider().replaceFirst(firstChar, firstChar.toUpperCase());</span>
    +<span class="nc" id="L89">                    providerList.put(OAUTH_2_AUTHORIZATION + oauth.getProvider(), clientName);</span>
    +                }
    +
    +<span class="nc" id="L92">                Client client = oauth.getClient();</span>
    +
    +<span class="nc bnc" id="L94" title="All 2 branches missed.">                if (client != null) {</span>
    +<span class="nc" id="L95">                    GoogleProvider google = client.getGoogle();</span>
    +
    +<span class="nc bnc" id="L97" title="All 2 branches missed.">                    if (validateProvider(google)) {</span>
    +<span class="nc" id="L98">                        providerList.put(</span>
    +<span class="nc" id="L99">                                OAUTH_2_AUTHORIZATION + google.getName(), google.getClientName());</span>
    +                    }
    +
    +<span class="nc" id="L102">                    GitHubProvider github = client.getGithub();</span>
    +
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">                    if (validateProvider(github)) {</span>
    +<span class="nc" id="L105">                        providerList.put(</span>
    +<span class="nc" id="L106">                                OAUTH_2_AUTHORIZATION + github.getName(), github.getClientName());</span>
    +                    }
    +
    +<span class="nc" id="L109">                    KeycloakProvider keycloak = client.getKeycloak();</span>
    +
    +<span class="nc bnc" id="L111" title="All 2 branches missed.">                    if (validateProvider(keycloak)) {</span>
    +<span class="nc" id="L112">                        providerList.put(</span>
    +<span class="nc" id="L113">                                OAUTH_2_AUTHORIZATION + keycloak.getName(),</span>
    +<span class="nc" id="L114">                                keycloak.getClientName());</span>
    +                    }
    +                }
    +            }
    +        }
    +
    +<span class="nc" id="L120">        SAML2 saml2 = securityProps.getSaml2();</span>
    +
    +<span class="nc bnc" id="L122" title="All 2 branches missed.">        if (securityProps.isSaml2Active()</span>
    +<span class="nc bnc" id="L123" title="All 2 branches missed.">                &amp;&amp; applicationProperties.getSystem().getEnableAlphaFunctionality()</span>
    +<span class="nc bnc" id="L124" title="All 2 branches missed.">                &amp;&amp; applicationProperties.getPremium().isEnabled()) {</span>
    +<span class="nc" id="L125">            String samlIdp = saml2.getProvider();</span>
    +<span class="nc" id="L126">            String saml2AuthenticationPath = &quot;/saml2/authenticate/&quot; + saml2.getRegistrationId();</span>
    +
    +<span class="nc bnc" id="L128" title="All 2 branches missed.">            if (applicationProperties.getPremium().getProFeatures().isSsoAutoLogin()) {</span>
    +<span class="nc" id="L129">                return &quot;redirect:&quot; + request.getRequestURL() + saml2AuthenticationPath;</span>
    +            } else {
    +<span class="nc" id="L131">                providerList.put(saml2AuthenticationPath, samlIdp + &quot; (SAML 2)&quot;);</span>
    +            }
    +        }
    +
    +        // Remove any null keys/values from the providerList
    +<span class="nc" id="L136">        providerList</span>
    +<span class="nc" id="L137">                .entrySet()</span>
    +<span class="nc bnc" id="L138" title="All 4 branches missed.">                .removeIf(entry -&gt; entry.getKey() == null || entry.getValue() == null);</span>
    +<span class="nc" id="L139">        model.addAttribute(&quot;providerList&quot;, providerList);</span>
    +<span class="nc" id="L140">        model.addAttribute(&quot;loginMethod&quot;, securityProps.getLoginMethod());</span>
    +
    +<span class="nc bnc" id="L142" title="All 2 branches missed.">        boolean altLogin = !providerList.isEmpty() ? securityProps.isAltLogin() : false;</span>
    +
    +<span class="nc" id="L144">        model.addAttribute(&quot;altLogin&quot;, altLogin);</span>
    +<span class="nc" id="L145">        model.addAttribute(&quot;currentPage&quot;, &quot;login&quot;);</span>
    +<span class="nc" id="L146">        String error = request.getParameter(&quot;error&quot;);</span>
    +
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">        if (error != null) {</span>
    +<span class="nc bnc" id="L149" title="All 4 branches missed.">            switch (error) {</span>
    +<span class="nc" id="L150">                case &quot;badCredentials&quot; -&gt; error = &quot;login.invalid&quot;;</span>
    +<span class="nc" id="L151">                case &quot;locked&quot; -&gt; error = &quot;login.locked&quot;;</span>
    +<span class="nc" id="L152">                case &quot;oauth2AuthenticationError&quot; -&gt; error = &quot;userAlreadyExistsOAuthMessage&quot;;</span>
    +            }
    +
    +<span class="nc" id="L155">            model.addAttribute(&quot;error&quot;, error);</span>
    +        }
    +
    +<span class="nc" id="L158">        String errorOAuth = request.getParameter(&quot;errorOAuth&quot;);</span>
    +
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">        if (errorOAuth != null) {</span>
    +<span class="nc bnc" id="L161" title="All 17 branches missed.">            switch (errorOAuth) {</span>
    +<span class="nc" id="L162">                case &quot;oAuth2AutoCreateDisabled&quot; -&gt; errorOAuth = &quot;login.oAuth2AutoCreateDisabled&quot;;</span>
    +<span class="nc" id="L163">                case &quot;invalidUsername&quot; -&gt; errorOAuth = &quot;login.invalid&quot;;</span>
    +<span class="nc" id="L164">                case &quot;userAlreadyExistsWeb&quot; -&gt; errorOAuth = &quot;userAlreadyExistsWebMessage&quot;;</span>
    +<span class="nc" id="L165">                case &quot;oAuth2AuthenticationErrorWeb&quot; -&gt; errorOAuth = &quot;login.oauth2InvalidUserType&quot;;</span>
    +<span class="nc" id="L166">                case &quot;invalid_token_response&quot; -&gt; errorOAuth = &quot;login.oauth2InvalidTokenResponse&quot;;</span>
    +                case &quot;authorization_request_not_found&quot; -&gt;
    +<span class="nc" id="L168">                        errorOAuth = &quot;login.oauth2RequestNotFound&quot;;</span>
    +<span class="nc" id="L169">                case &quot;access_denied&quot; -&gt; errorOAuth = &quot;login.oauth2AccessDenied&quot;;</span>
    +                case &quot;invalid_user_info_response&quot; -&gt;
    +<span class="nc" id="L171">                        errorOAuth = &quot;login.oauth2InvalidUserInfoResponse&quot;;</span>
    +<span class="nc" id="L172">                case &quot;invalid_request&quot; -&gt; errorOAuth = &quot;login.oauth2invalidRequest&quot;;</span>
    +<span class="nc" id="L173">                case &quot;invalid_id_token&quot; -&gt; errorOAuth = &quot;login.oauth2InvalidIdToken&quot;;</span>
    +<span class="nc" id="L174">                case &quot;oAuth2AdminBlockedUser&quot; -&gt; errorOAuth = &quot;login.oAuth2AdminBlockedUser&quot;;</span>
    +<span class="nc" id="L175">                case &quot;userIsDisabled&quot; -&gt; errorOAuth = &quot;login.userIsDisabled&quot;;</span>
    +<span class="nc" id="L176">                case &quot;invalid_destination&quot; -&gt; errorOAuth = &quot;login.invalid_destination&quot;;</span>
    +                case &quot;relying_party_registration_not_found&quot; -&gt;
    +<span class="nc" id="L178">                        errorOAuth = &quot;login.relyingPartyRegistrationNotFound&quot;;</span>
    +                // Valid InResponseTo was not available from the validation context, unable to
    +                // evaluate
    +<span class="nc" id="L181">                case &quot;invalid_in_response_to&quot; -&gt; errorOAuth = &quot;login.invalid_in_response_to&quot;;</span>
    +                case &quot;not_authentication_provider_found&quot; -&gt;
    +<span class="nc" id="L183">                        errorOAuth = &quot;login.not_authentication_provider_found&quot;;</span>
    +            }
    +
    +<span class="nc" id="L186">            model.addAttribute(&quot;errorOAuth&quot;, errorOAuth);</span>
    +        }
    +
    +<span class="nc bnc" id="L189" title="All 2 branches missed.">        if (request.getParameter(&quot;messageType&quot;) != null) {</span>
    +<span class="nc" id="L190">            model.addAttribute(&quot;messageType&quot;, &quot;changedCredsMessage&quot;);</span>
    +        }
    +
    +<span class="nc bnc" id="L193" title="All 2 branches missed.">        if (request.getParameter(&quot;logout&quot;) != null) {</span>
    +<span class="nc" id="L194">            model.addAttribute(&quot;logoutMessage&quot;, &quot;You have been logged out.&quot;);</span>
    +        }
    +
    +<span class="nc" id="L197">        return &quot;login&quot;;</span>
    +    }
    +
    +    @PreAuthorize(&quot;hasRole('ROLE_ADMIN')&quot;)
    +    @GetMapping(&quot;/usage&quot;)
    +    public String showUsage() {
    +<span class="nc bnc" id="L203" title="All 2 branches missed.">        if (!runningEE) {</span>
    +<span class="nc" id="L204">            return &quot;error&quot;;</span>
    +        }
    +<span class="nc" id="L206">        return &quot;usage&quot;;</span>
    +    }
    +
    +    @PreAuthorize(&quot;hasRole('ROLE_ADMIN')&quot;)
    +    @GetMapping(&quot;/adminSettings&quot;)
    +    public String showAddUserForm(
    +            HttpServletRequest request, Model model, Authentication authentication) {
    +<span class="nc" id="L213">        List&lt;User&gt; allUsers = userRepository.findAll();</span>
    +<span class="nc" id="L214">        Iterator&lt;User&gt; iterator = allUsers.iterator();</span>
    +<span class="nc" id="L215">        Map&lt;String, String&gt; roleDetails = Role.getAllRoleDetails();</span>
    +        // Map to store session information and user activity status
    +<span class="nc" id="L217">        Map&lt;String, Boolean&gt; userSessions = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L218">        Map&lt;String, Date&gt; userLastRequest = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L219">        int activeUsers = 0;</span>
    +<span class="nc" id="L220">        int disabledUsers = 0;</span>
    +<span class="nc bnc" id="L221" title="All 2 branches missed.">        while (iterator.hasNext()) {</span>
    +<span class="nc" id="L222">            User user = iterator.next();</span>
    +<span class="nc bnc" id="L223" title="All 2 branches missed.">            if (user != null) {</span>
    +<span class="nc bnc" id="L224" title="All 2 branches missed.">                for (Authority authority : user.getAuthorities()) {</span>
    +<span class="nc bnc" id="L225" title="All 2 branches missed.">                    if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {</span>
    +<span class="nc" id="L226">                        iterator.remove();</span>
    +<span class="nc" id="L227">                        roleDetails.remove(Role.INTERNAL_API_USER.getRoleId());</span>
    +                        // Break out of the inner loop once the user is removed
    +<span class="nc" id="L229">                        break;</span>
    +                    }
    +<span class="nc" id="L231">                }</span>
    +                // Determine the user's session status and last request time
    +<span class="nc" id="L233">                int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();</span>
    +<span class="nc" id="L234">                boolean hasActiveSession = false;</span>
    +<span class="nc" id="L235">                Date lastRequest = null;</span>
    +<span class="nc" id="L236">                Optional&lt;SessionEntity&gt; latestSession =</span>
    +<span class="nc" id="L237">                        sessionPersistentRegistry.findLatestSession(user.getUsername());</span>
    +<span class="nc bnc" id="L238" title="All 2 branches missed.">                if (latestSession.isPresent()) {</span>
    +<span class="nc" id="L239">                    SessionEntity sessionEntity = latestSession.get();</span>
    +<span class="nc" id="L240">                    Date lastAccessedTime = sessionEntity.getLastRequest();</span>
    +<span class="nc" id="L241">                    Instant now = Instant.now();</span>
    +                    // Calculate session expiration and update session status accordingly
    +<span class="nc" id="L243">                    Instant expirationTime =</span>
    +                            lastAccessedTime
    +<span class="nc" id="L245">                                    .toInstant()</span>
    +<span class="nc" id="L246">                                    .plus(maxInactiveInterval, ChronoUnit.SECONDS);</span>
    +<span class="nc bnc" id="L247" title="All 2 branches missed.">                    if (now.isAfter(expirationTime)) {</span>
    +<span class="nc" id="L248">                        sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());</span>
    +                    } else {
    +<span class="nc bnc" id="L250" title="All 2 branches missed.">                        hasActiveSession = !sessionEntity.isExpired();</span>
    +                    }
    +<span class="nc" id="L252">                    lastRequest = sessionEntity.getLastRequest();</span>
    +<span class="nc" id="L253">                } else {</span>
    +                    // No session, set default last request time
    +<span class="nc" id="L255">                    lastRequest = new Date(0);</span>
    +                }
    +<span class="nc" id="L257">                userSessions.put(user.getUsername(), hasActiveSession);</span>
    +<span class="nc" id="L258">                userLastRequest.put(user.getUsername(), lastRequest);</span>
    +<span class="nc bnc" id="L259" title="All 2 branches missed.">                if (hasActiveSession) {</span>
    +<span class="nc" id="L260">                    activeUsers++;</span>
    +                }
    +<span class="nc bnc" id="L262" title="All 2 branches missed.">                if (!user.isEnabled()) {</span>
    +<span class="nc" id="L263">                    disabledUsers++;</span>
    +                }
    +            }
    +<span class="nc" id="L266">        }</span>
    +        // Sort users by active status and last request date
    +<span class="nc" id="L268">        List&lt;User&gt; sortedUsers =</span>
    +<span class="nc" id="L269">                allUsers.stream()</span>
    +<span class="nc" id="L270">                        .sorted(</span>
    +                                (u1, u2) -&gt; {
    +<span class="nc" id="L272">                                    boolean u1Active = userSessions.get(u1.getUsername());</span>
    +<span class="nc" id="L273">                                    boolean u2Active = userSessions.get(u2.getUsername());</span>
    +<span class="nc bnc" id="L274" title="All 4 branches missed.">                                    if (u1Active &amp;&amp; !u2Active) {</span>
    +<span class="nc" id="L275">                                        return -1;</span>
    +<span class="nc bnc" id="L276" title="All 4 branches missed.">                                    } else if (!u1Active &amp;&amp; u2Active) {</span>
    +<span class="nc" id="L277">                                        return 1;</span>
    +                                    } else {
    +<span class="nc" id="L279">                                        Date u1LastRequest =</span>
    +<span class="nc" id="L280">                                                userLastRequest.getOrDefault(</span>
    +<span class="nc" id="L281">                                                        u1.getUsername(), new Date(0));</span>
    +<span class="nc" id="L282">                                        Date u2LastRequest =</span>
    +<span class="nc" id="L283">                                                userLastRequest.getOrDefault(</span>
    +<span class="nc" id="L284">                                                        u2.getUsername(), new Date(0));</span>
    +<span class="nc" id="L285">                                        return u2LastRequest.compareTo(u1LastRequest);</span>
    +                                    }
    +                                })
    +<span class="nc" id="L288">                        .toList();</span>
    +<span class="nc" id="L289">        String messageType = request.getParameter(&quot;messageType&quot;);</span>
    +
    +        String deleteMessage;
    +<span class="nc bnc" id="L292" title="All 2 branches missed.">        if (messageType != null) {</span>
    +            deleteMessage =
    +<span class="nc bnc" id="L294" title="All 3 branches missed.">                    switch (messageType) {</span>
    +<span class="nc" id="L295">                        case &quot;deleteCurrentUser&quot; -&gt; &quot;deleteCurrentUserMessage&quot;;</span>
    +<span class="nc" id="L296">                        case &quot;deleteUsernameExists&quot; -&gt; &quot;deleteUsernameExistsMessage&quot;;</span>
    +<span class="nc" id="L297">                        default -&gt; null;</span>
    +                    };
    +
    +<span class="nc" id="L300">            model.addAttribute(&quot;deleteMessage&quot;, deleteMessage);</span>
    +
    +            String addMessage;
    +            addMessage =
    +<span class="nc bnc" id="L304" title="All 4 branches missed.">                    switch (messageType) {</span>
    +<span class="nc" id="L305">                        case &quot;usernameExists&quot; -&gt; &quot;usernameExistsMessage&quot;;</span>
    +<span class="nc" id="L306">                        case &quot;invalidUsername&quot; -&gt; &quot;invalidUsernameMessage&quot;;</span>
    +<span class="nc" id="L307">                        case &quot;invalidPassword&quot; -&gt; &quot;invalidPasswordMessage&quot;;</span>
    +<span class="nc" id="L308">                        default -&gt; null;</span>
    +                    };
    +<span class="nc" id="L310">            model.addAttribute(&quot;addMessage&quot;, addMessage);</span>
    +        }
    +
    +        String changeMessage;
    +<span class="nc bnc" id="L314" title="All 2 branches missed.">        if (messageType != null) {</span>
    +            changeMessage =
    +<span class="nc bnc" id="L316" title="All 4 branches missed.">                    switch (messageType) {</span>
    +<span class="nc" id="L317">                        case &quot;userNotFound&quot; -&gt; &quot;userNotFoundMessage&quot;;</span>
    +<span class="nc" id="L318">                        case &quot;downgradeCurrentUser&quot; -&gt; &quot;downgradeCurrentUserMessage&quot;;</span>
    +<span class="nc" id="L319">                        case &quot;disabledCurrentUser&quot; -&gt; &quot;disabledCurrentUserMessage&quot;;</span>
    +<span class="nc" id="L320">                        default -&gt; messageType;</span>
    +                    };
    +<span class="nc" id="L322">            model.addAttribute(&quot;changeMessage&quot;, changeMessage);</span>
    +        }
    +
    +<span class="nc" id="L325">        model.addAttribute(&quot;users&quot;, sortedUsers);</span>
    +<span class="nc" id="L326">        model.addAttribute(&quot;currentUsername&quot;, authentication.getName());</span>
    +<span class="nc" id="L327">        model.addAttribute(&quot;roleDetails&quot;, roleDetails);</span>
    +<span class="nc" id="L328">        model.addAttribute(&quot;userSessions&quot;, userSessions);</span>
    +<span class="nc" id="L329">        model.addAttribute(&quot;userLastRequest&quot;, userLastRequest);</span>
    +<span class="nc" id="L330">        model.addAttribute(&quot;totalUsers&quot;, allUsers.size());</span>
    +<span class="nc" id="L331">        model.addAttribute(&quot;activeUsers&quot;, activeUsers);</span>
    +<span class="nc" id="L332">        model.addAttribute(&quot;disabledUsers&quot;, disabledUsers);</span>
    +
    +<span class="nc" id="L334">        model.addAttribute(&quot;maxPaidUsers&quot;, applicationProperties.getPremium().getMaxUsers());</span>
    +<span class="nc" id="L335">        return &quot;adminSettings&quot;;</span>
    +    }
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @GetMapping(&quot;/account&quot;)
    +    public String account(HttpServletRequest request, Model model, Authentication authentication) {
    +<span class="nc bnc" id="L341" title="All 4 branches missed.">        if (authentication == null || !authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L342">            return &quot;redirect:/&quot;;</span>
    +        }
    +<span class="nc bnc" id="L344" title="All 2 branches missed.">        if (authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L345">            Object principal = authentication.getPrincipal();</span>
    +<span class="nc" id="L346">            String username = null;</span>
    +
    +            // Retrieve username and other attributes and add login attributes to the model
    +<span class="nc bnc" id="L349" title="All 2 branches missed.">            if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L350">                username = detailsUser.getUsername();</span>
    +<span class="nc" id="L351">                model.addAttribute(&quot;oAuth2Login&quot;, false);</span>
    +            }
    +<span class="nc bnc" id="L353" title="All 2 branches missed.">            if (principal instanceof OAuth2User oAuth2User) {</span>
    +<span class="nc" id="L354">                username = oAuth2User.getName();</span>
    +<span class="nc" id="L355">                model.addAttribute(&quot;oAuth2Login&quot;, true);</span>
    +            }
    +<span class="nc bnc" id="L357" title="All 2 branches missed.">            if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {</span>
    +<span class="nc" id="L358">                username = saml2User.name();</span>
    +<span class="nc" id="L359">                model.addAttribute(&quot;saml2Login&quot;, true);</span>
    +            }
    +<span class="nc bnc" id="L361" title="All 2 branches missed.">            if (username != null) {</span>
    +                // Fetch user details from the database
    +<span class="nc" id="L363">                Optional&lt;User&gt; user = userRepository.findByUsernameIgnoreCaseWithSettings(username);</span>
    +
    +<span class="nc bnc" id="L365" title="All 2 branches missed.">                if (user.isEmpty()) {</span>
    +<span class="nc" id="L366">                    return &quot;redirect:/error&quot;;</span>
    +                }
    +
    +                // Convert settings map to JSON string
    +<span class="nc" id="L370">                ObjectMapper objectMapper = new ObjectMapper();</span>
    +                String settingsJson;
    +                try {
    +<span class="nc" id="L373">                    settingsJson = objectMapper.writeValueAsString(user.get().getSettings());</span>
    +<span class="nc" id="L374">                } catch (JsonProcessingException e) {</span>
    +<span class="nc" id="L375">                    log.error(&quot;Error converting settings map&quot;, e);</span>
    +<span class="nc" id="L376">                    return &quot;redirect:/error&quot;;</span>
    +<span class="nc" id="L377">                }</span>
    +
    +<span class="nc" id="L379">                String messageType = request.getParameter(&quot;messageType&quot;);</span>
    +<span class="nc bnc" id="L380" title="All 2 branches missed.">                if (messageType != null) {</span>
    +<span class="nc bnc" id="L381" title="All 6 branches missed.">                    switch (messageType) {</span>
    +<span class="nc" id="L382">                        case &quot;notAuthenticated&quot; -&gt; messageType = &quot;notAuthenticatedMessage&quot;;</span>
    +<span class="nc" id="L383">                        case &quot;userNotFound&quot; -&gt; messageType = &quot;userNotFoundMessage&quot;;</span>
    +<span class="nc" id="L384">                        case &quot;incorrectPassword&quot; -&gt; messageType = &quot;incorrectPasswordMessage&quot;;</span>
    +<span class="nc" id="L385">                        case &quot;usernameExists&quot; -&gt; messageType = &quot;usernameExistsMessage&quot;;</span>
    +<span class="nc" id="L386">                        case &quot;invalidUsername&quot; -&gt; messageType = &quot;invalidUsernameMessage&quot;;</span>
    +                    }
    +                }
    +
    +<span class="nc" id="L390">                model.addAttribute(&quot;username&quot;, username);</span>
    +<span class="nc" id="L391">                model.addAttribute(&quot;messageType&quot;, messageType);</span>
    +<span class="nc" id="L392">                model.addAttribute(&quot;role&quot;, user.get().getRolesAsString());</span>
    +<span class="nc" id="L393">                model.addAttribute(&quot;settings&quot;, settingsJson);</span>
    +<span class="nc" id="L394">                model.addAttribute(&quot;changeCredsFlag&quot;, user.get().isFirstLogin());</span>
    +<span class="nc" id="L395">                model.addAttribute(&quot;currentPage&quot;, &quot;account&quot;);</span>
    +            }
    +<span class="nc" id="L397">        } else {</span>
    +<span class="nc" id="L398">            return &quot;redirect:/&quot;;</span>
    +        }
    +<span class="nc" id="L400">        return &quot;account&quot;;</span>
    +    }
    +
    +    @PreAuthorize(&quot;!hasAuthority('ROLE_DEMO_USER')&quot;)
    +    @GetMapping(&quot;/change-creds&quot;)
    +    public String changeCreds(
    +            HttpServletRequest request, Model model, Authentication authentication) {
    +<span class="nc bnc" id="L407" title="All 4 branches missed.">        if (authentication == null || !authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L408">            return &quot;redirect:/&quot;;</span>
    +        }
    +<span class="nc bnc" id="L410" title="All 2 branches missed.">        if (authentication.isAuthenticated()) {</span>
    +<span class="nc" id="L411">            Object principal = authentication.getPrincipal();</span>
    +<span class="nc bnc" id="L412" title="All 2 branches missed.">            if (principal instanceof UserDetails detailsUser) {</span>
    +<span class="nc" id="L413">                String username = detailsUser.getUsername();</span>
    +                // Fetch user details from the database
    +<span class="nc" id="L415">                Optional&lt;User&gt; user = userRepository.findByUsernameIgnoreCase(username);</span>
    +<span class="nc bnc" id="L416" title="All 2 branches missed.">                if (user.isEmpty()) {</span>
    +                    // Handle error appropriately, example redirection in case of error
    +<span class="nc" id="L418">                    return &quot;redirect:/error&quot;;</span>
    +                }
    +<span class="nc" id="L420">                String messageType = request.getParameter(&quot;messageType&quot;);</span>
    +<span class="nc bnc" id="L421" title="All 2 branches missed.">                if (messageType != null) {</span>
    +<span class="nc bnc" id="L422" title="All 5 branches missed.">                    switch (messageType) {</span>
    +                        case &quot;notAuthenticated&quot;:
    +<span class="nc" id="L424">                            messageType = &quot;notAuthenticatedMessage&quot;;</span>
    +<span class="nc" id="L425">                            break;</span>
    +                        case &quot;userNotFound&quot;:
    +<span class="nc" id="L427">                            messageType = &quot;userNotFoundMessage&quot;;</span>
    +<span class="nc" id="L428">                            break;</span>
    +                        case &quot;incorrectPassword&quot;:
    +<span class="nc" id="L430">                            messageType = &quot;incorrectPasswordMessage&quot;;</span>
    +<span class="nc" id="L431">                            break;</span>
    +                        case &quot;usernameExists&quot;:
    +<span class="nc" id="L433">                            messageType = &quot;usernameExistsMessage&quot;;</span>
    +<span class="nc" id="L434">                            break;</span>
    +                        default:
    +                            break;
    +                    }
    +<span class="nc" id="L438">                    model.addAttribute(&quot;messageType&quot;, messageType);</span>
    +                }
    +
    +<span class="nc" id="L441">                model.addAttribute(&quot;username&quot;, username);</span>
    +            }
    +<span class="nc" id="L443">        } else {</span>
    +<span class="nc" id="L444">            return &quot;redirect:/&quot;;</span>
    +        }
    +<span class="nc" id="L446">        return &quot;change-creds&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/ConverterWebController.html b/testResult/html/stirling.software.SPDF.controller.web/ConverterWebController.html
    new file mode 100644
    index 000000000..c5d10111c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/ConverterWebController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConverterWebController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">ConverterWebController</span></div><h1>ConverterWebController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">139 of 139</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">37</td><td class="ctr2">37</td><td class="ctr1">15</td><td class="ctr2">15</td></tr></tfoot><tbody><tr><td id="a9"><a href="ConverterWebController.java.html#L64" class="el_method">pdfToimgForm(Model)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="15" alt="15"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a8"><a href="ConverterWebController.java.html#L73" class="el_method">pdfToHTML()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a11"><a href="ConverterWebController.java.html#L81" class="el_method">pdfToPresentation()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="12" alt="12"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a12"><a href="ConverterWebController.java.html#L89" class="el_method">pdfToText()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="12" alt="12"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a13"><a href="ConverterWebController.java.html#L97" class="el_method">pdfToWord()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="12" alt="12"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">3</td><td class="ctr2" id="i4">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a14"><a href="ConverterWebController.java.html#L105" class="el_method">pdfToXML()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="12" alt="12"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a7"><a href="ConverterWebController.java.html#L113" class="el_method">pdfToCSV()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="12" alt="12"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="ConverterWebController.java.html#L20" class="el_method">convertImgToPdfForm(Model)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">2</td><td class="ctr2" id="i7">2</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a1"><a href="ConverterWebController.java.html#L27" class="el_method">convertHTMLToPdfForm(Model)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="7" alt="7"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a3"><a href="ConverterWebController.java.html#L34" class="el_method">convertMarkdownToPdfForm(Model)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="7" alt="7"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a4"><a href="ConverterWebController.java.html#L41" class="el_method">convertPdfToMarkdownForm(Model)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="7" alt="7"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">2</td><td class="ctr2" id="i10">2</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a6"><a href="ConverterWebController.java.html#L48" class="el_method">convertURLToPdfForm(Model)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="7" alt="7"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">2</td><td class="ctr2" id="i11">2</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a5"><a href="ConverterWebController.java.html#L55" class="el_method">convertToPdfForm(Model)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="7" alt="7"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h12">2</td><td class="ctr2" id="i12">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a10"><a href="ConverterWebController.java.html#L121" class="el_method">pdfToPdfAForm(Model)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="7" alt="7"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">2</td><td class="ctr2" id="i13">2</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a0"><a href="ConverterWebController.java.html#L15" class="el_method">ConverterWebController()</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="3" alt="3"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h14">1</td><td class="ctr2" id="i14">1</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/ConverterWebController.java.html b/testResult/html/stirling.software.SPDF.controller.web/ConverterWebController.java.html
    new file mode 100644
    index 000000000..d09e01b69
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/ConverterWebController.java.html
    @@ -0,0 +1,125 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConverterWebController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">ConverterWebController.java</span></div><h1>ConverterWebController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import org.springframework.stereotype.Controller;
    +import org.springframework.ui.Model;
    +import org.springframework.web.bind.annotation.GetMapping;
    +import org.springframework.web.servlet.ModelAndView;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.utils.CheckProgramInstall;
    +
    +@Controller
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +<span class="nc" id="L15">public class ConverterWebController {</span>
    +
    +    @GetMapping(&quot;/img-to-pdf&quot;)
    +    @Hidden
    +    public String convertImgToPdfForm(Model model) {
    +<span class="nc" id="L20">        model.addAttribute(&quot;currentPage&quot;, &quot;img-to-pdf&quot;);</span>
    +<span class="nc" id="L21">        return &quot;convert/img-to-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/html-to-pdf&quot;)
    +    @Hidden
    +    public String convertHTMLToPdfForm(Model model) {
    +<span class="nc" id="L27">        model.addAttribute(&quot;currentPage&quot;, &quot;html-to-pdf&quot;);</span>
    +<span class="nc" id="L28">        return &quot;convert/html-to-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/markdown-to-pdf&quot;)
    +    @Hidden
    +    public String convertMarkdownToPdfForm(Model model) {
    +<span class="nc" id="L34">        model.addAttribute(&quot;currentPage&quot;, &quot;markdown-to-pdf&quot;);</span>
    +<span class="nc" id="L35">        return &quot;convert/markdown-to-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-markdown&quot;)
    +    @Hidden
    +    public String convertPdfToMarkdownForm(Model model) {
    +<span class="nc" id="L41">        model.addAttribute(&quot;currentPage&quot;, &quot;pdf-to-markdown&quot;);</span>
    +<span class="nc" id="L42">        return &quot;convert/pdf-to-markdown&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/url-to-pdf&quot;)
    +    @Hidden
    +    public String convertURLToPdfForm(Model model) {
    +<span class="nc" id="L48">        model.addAttribute(&quot;currentPage&quot;, &quot;url-to-pdf&quot;);</span>
    +<span class="nc" id="L49">        return &quot;convert/url-to-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/file-to-pdf&quot;)
    +    @Hidden
    +    public String convertToPdfForm(Model model) {
    +<span class="nc" id="L55">        model.addAttribute(&quot;currentPage&quot;, &quot;file-to-pdf&quot;);</span>
    +<span class="nc" id="L56">        return &quot;convert/file-to-pdf&quot;;</span>
    +    }
    +
    +    // PDF TO......
    +
    +    @GetMapping(&quot;/pdf-to-img&quot;)
    +    @Hidden
    +    public String pdfToimgForm(Model model) {
    +<span class="nc" id="L64">        boolean isPython = CheckProgramInstall.isPythonAvailable();</span>
    +<span class="nc" id="L65">        model.addAttribute(&quot;isPython&quot;, isPython);</span>
    +<span class="nc" id="L66">        model.addAttribute(&quot;currentPage&quot;, &quot;pdf-to-img&quot;);</span>
    +<span class="nc" id="L67">        return &quot;convert/pdf-to-img&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-html&quot;)
    +    @Hidden
    +    public ModelAndView pdfToHTML() {
    +<span class="nc" id="L73">        ModelAndView modelAndView = new ModelAndView(&quot;convert/pdf-to-html&quot;);</span>
    +<span class="nc" id="L74">        modelAndView.addObject(&quot;currentPage&quot;, &quot;pdf-to-html&quot;);</span>
    +<span class="nc" id="L75">        return modelAndView;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-presentation&quot;)
    +    @Hidden
    +    public ModelAndView pdfToPresentation() {
    +<span class="nc" id="L81">        ModelAndView modelAndView = new ModelAndView(&quot;convert/pdf-to-presentation&quot;);</span>
    +<span class="nc" id="L82">        modelAndView.addObject(&quot;currentPage&quot;, &quot;pdf-to-presentation&quot;);</span>
    +<span class="nc" id="L83">        return modelAndView;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-text&quot;)
    +    @Hidden
    +    public ModelAndView pdfToText() {
    +<span class="nc" id="L89">        ModelAndView modelAndView = new ModelAndView(&quot;convert/pdf-to-text&quot;);</span>
    +<span class="nc" id="L90">        modelAndView.addObject(&quot;currentPage&quot;, &quot;pdf-to-text&quot;);</span>
    +<span class="nc" id="L91">        return modelAndView;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-word&quot;)
    +    @Hidden
    +    public ModelAndView pdfToWord() {
    +<span class="nc" id="L97">        ModelAndView modelAndView = new ModelAndView(&quot;convert/pdf-to-word&quot;);</span>
    +<span class="nc" id="L98">        modelAndView.addObject(&quot;currentPage&quot;, &quot;pdf-to-word&quot;);</span>
    +<span class="nc" id="L99">        return modelAndView;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-xml&quot;)
    +    @Hidden
    +    public ModelAndView pdfToXML() {
    +<span class="nc" id="L105">        ModelAndView modelAndView = new ModelAndView(&quot;convert/pdf-to-xml&quot;);</span>
    +<span class="nc" id="L106">        modelAndView.addObject(&quot;currentPage&quot;, &quot;pdf-to-xml&quot;);</span>
    +<span class="nc" id="L107">        return modelAndView;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-csv&quot;)
    +    @Hidden
    +    public ModelAndView pdfToCSV() {
    +<span class="nc" id="L113">        ModelAndView modelAndView = new ModelAndView(&quot;convert/pdf-to-csv&quot;);</span>
    +<span class="nc" id="L114">        modelAndView.addObject(&quot;currentPage&quot;, &quot;pdf-to-csv&quot;);</span>
    +<span class="nc" id="L115">        return modelAndView;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-pdfa&quot;)
    +    @Hidden
    +    public String pdfToPdfAForm(Model model) {
    +<span class="nc" id="L121">        model.addAttribute(&quot;currentPage&quot;, &quot;pdf-to-pdfa&quot;);</span>
    +<span class="nc" id="L122">        return &quot;convert/pdf-to-pdfa&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/DatabaseWebController.html b/testResult/html/stirling.software.SPDF.controller.web/DatabaseWebController.html
    new file mode 100644
    index 000000000..3f8b48128
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/DatabaseWebController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DatabaseWebController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">DatabaseWebController</span></div><h1>DatabaseWebController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">58 of 58</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">16</td><td class="ctr2">16</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="DatabaseWebController.java.html#L31" class="el_method">database(HttpServletRequest, Model, Authentication)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="52" alt="52"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">13</td><td class="ctr2" id="i0">13</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="DatabaseWebController.java.html#L24" class="el_method">DatabaseWebController(DatabaseService)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/DatabaseWebController.java.html b/testResult/html/stirling.software.SPDF.controller.web/DatabaseWebController.java.html
    new file mode 100644
    index 000000000..c10c98f58
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/DatabaseWebController.java.html
    @@ -0,0 +1,48 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DatabaseWebController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">DatabaseWebController.java</span></div><h1>DatabaseWebController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import java.util.List;
    +
    +import org.springframework.security.access.prepost.PreAuthorize;
    +import org.springframework.security.core.Authentication;
    +import org.springframework.stereotype.Controller;
    +import org.springframework.ui.Model;
    +import org.springframework.web.bind.annotation.GetMapping;
    +
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +
    +import stirling.software.SPDF.config.security.database.DatabaseService;
    +import stirling.software.SPDF.utils.FileInfo;
    +
    +@Controller
    +@Tag(name = &quot;Database Management&quot;, description = &quot;Database management and security APIs&quot;)
    +public class DatabaseWebController {
    +
    +    private final DatabaseService databaseService;
    +
    +<span class="nc" id="L24">    public DatabaseWebController(DatabaseService databaseService) {</span>
    +<span class="nc" id="L25">        this.databaseService = databaseService;</span>
    +<span class="nc" id="L26">    }</span>
    +
    +    @PreAuthorize(&quot;hasRole('ROLE_ADMIN')&quot;)
    +    @GetMapping(&quot;/database&quot;)
    +    public String database(HttpServletRequest request, Model model, Authentication authentication) {
    +<span class="nc" id="L31">        String error = request.getParameter(&quot;error&quot;);</span>
    +<span class="nc" id="L32">        String confirmed = request.getParameter(&quot;infoMessage&quot;);</span>
    +<span class="nc bnc" id="L33" title="All 2 branches missed.">        if (error != null) {</span>
    +<span class="nc" id="L34">            model.addAttribute(&quot;error&quot;, error);</span>
    +<span class="nc bnc" id="L35" title="All 2 branches missed.">        } else if (confirmed != null) {</span>
    +<span class="nc" id="L36">            model.addAttribute(&quot;infoMessage&quot;, confirmed);</span>
    +        }
    +<span class="nc" id="L38">        List&lt;FileInfo&gt; backupList = databaseService.getBackupList();</span>
    +<span class="nc" id="L39">        model.addAttribute(&quot;backupFiles&quot;, backupList);</span>
    +<span class="nc" id="L40">        String dbVersion = databaseService.getH2Version();</span>
    +<span class="nc" id="L41">        model.addAttribute(&quot;databaseVersion&quot;, dbVersion);</span>
    +<span class="nc bnc" id="L42" title="All 2 branches missed.">        if (&quot;Unknown&quot;.equalsIgnoreCase(dbVersion)) {</span>
    +<span class="nc" id="L43">            model.addAttribute(&quot;infoMessage&quot;, &quot;notSupported&quot;);</span>
    +        }
    +<span class="nc" id="L45">        return &quot;database&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController$1.html b/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController$1.html
    new file mode 100644
    index 000000000..353b0dc6d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GeneralWebController.new TypeReference() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">GeneralWebController.new TypeReference() {...}</span></div><h1>GeneralWebController.new TypeReference() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="GeneralWebController.java.html#L75" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController$FontResource.html b/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController$FontResource.html
    new file mode 100644
    index 000000000..617a9b8c3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController$FontResource.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GeneralWebController.FontResource</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">GeneralWebController.FontResource</span></div><h1>GeneralWebController.FontResource</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">38 of 38</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a0"><a href="GeneralWebController.java.html#L316" class="el_method">GeneralWebController.FontResource(GeneralWebController, String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="17" alt="17"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="GeneralWebController.java.html#L327" class="el_method">setName(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="GeneralWebController.java.html#L335" class="el_method">setExtension(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="GeneralWebController.java.html#L343" class="el_method">setType(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="GeneralWebController.java.html#L323" class="el_method">getName()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="GeneralWebController.java.html#L331" class="el_method">getExtension()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a3"><a href="GeneralWebController.java.html#L339" class="el_method">getType()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController.html b/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController.html
    new file mode 100644
    index 000000000..9ae7c39a4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GeneralWebController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">GeneralWebController</span></div><h1>GeneralWebController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">451 of 451</td><td class="ctr2">0%</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="ctr1">42</td><td class="ctr2">42</td><td class="ctr1">122</td><td class="ctr2">122</td><td class="ctr1">28</td><td class="ctr2">28</td></tr></tfoot><tbody><tr><td id="a17"><a href="GeneralWebController.java.html#L58" class="el_method">pipelineForm(Model)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="163" alt="163"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h0">40</td><td class="ctr2" id="i0">40</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a7"><a href="GeneralWebController.java.html#L248" class="el_method">lambda$getFontNamesFromLocation$2(Resource)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="39" alt="39"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h1">10</td><td class="ctr2" id="i1">10</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a21"><a href="GeneralWebController.java.html#L185" class="el_method">signForm(Model)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="32" alt="32"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h2">8</td><td class="ctr2" id="i2">8</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a5"><a href="GeneralWebController.java.html#L242" class="el_method">getFontNamesFromLocation(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="22" alt="22"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">8</td><td class="ctr2" id="i3">8</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="GeneralWebController.java.html#L226" class="el_method">getFontNames()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="20" alt="20"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="GeneralWebController.java.html#L270" class="el_method">getFormatFromExtension(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="20" alt="20"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h4">7</td><td class="ctr2" id="i4">7</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a3"><a href="GeneralWebController.java.html#L48" class="el_method">GeneralWebController(SignatureService, UserServiceInterface, ResourceLoader, RuntimePathConfig)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="15" alt="15"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">6</td><td class="ctr2" id="i6">6</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a10"><a href="GeneralWebController.java.html#L108" class="el_method">mergePdfForm(Model)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">2</td><td class="ctr2" id="i7">2</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a24"><a href="GeneralWebController.java.html#L115" class="el_method">splitPdfBySections(Model)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a23"><a href="GeneralWebController.java.html#L122" class="el_method">splitPdfByChapters(Model)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a27"><a href="GeneralWebController.java.html#L129" class="el_method">ViewPdfForm2(Model)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">2</td><td class="ctr2" id="i10">2</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a12"><a href="GeneralWebController.java.html#L136" class="el_method">multiToolForm(Model)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">2</td><td class="ctr2" id="i11">2</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a14"><a href="GeneralWebController.java.html#L143" class="el_method">pageDeleter(Model)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h12">2</td><td class="ctr2" id="i12">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a15"><a href="GeneralWebController.java.html#L150" class="el_method">pageOrganizer(Model)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">2</td><td class="ctr2" id="i13">2</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a2"><a href="GeneralWebController.java.html#L157" class="el_method">extractPages(Model)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h14">2</td><td class="ctr2" id="i14">2</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a16"><a href="GeneralWebController.java.html#L164" class="el_method">pdfToSinglePage(Model)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h15">2</td><td class="ctr2" id="i15">2</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a19"><a href="GeneralWebController.java.html#L171" class="el_method">rotatePdfForm(Model)</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h16">2</td><td class="ctr2" id="i16">2</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a25"><a href="GeneralWebController.java.html#L178" class="el_method">splitPdfForm(Model)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h17">2</td><td class="ctr2" id="i17">2</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a11"><a href="GeneralWebController.java.html#L200" class="el_method">multiPageLayoutForm(Model)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h18">2</td><td class="ctr2" id="i18">2</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a20"><a href="GeneralWebController.java.html#L207" class="el_method">scalePagesFrom(Model)</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h19">2</td><td class="ctr2" id="i19">2</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a22"><a href="GeneralWebController.java.html#L214" class="el_method">splitBySizeOrCount(Model)</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">2</td><td class="ctr2" id="i20">2</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a13"><a href="GeneralWebController.java.html#L221" class="el_method">overlayPdf(Model)</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">2</td><td class="ctr2" id="i21">2</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a1"><a href="GeneralWebController.java.html#L290" class="el_method">cropForm(Model)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h22">2</td><td class="ctr2" id="i22">2</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a0"><a href="GeneralWebController.java.html#L297" class="el_method">autoSPlitPDFForm(Model)</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">2</td><td class="ctr2" id="i23">2</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a18"><a href="GeneralWebController.java.html#L304" class="el_method">removeImagePdfForm(Model)</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h24">2</td><td class="ctr2" id="i24">2</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr><tr><td id="a9"><a href="GeneralWebController.java.html#L66" class="el_method">lambda$pipelineForm$1(Path)</a></td><td class="bar" id="b25"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="5" alt="5"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g25">1</td><td class="ctr1" id="h25">1</td><td class="ctr2" id="i25">1</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k25">1</td></tr><tr><td id="a8"><a href="GeneralWebController.java.html#L65" class="el_method">lambda$pipelineForm$0(Path)</a></td><td class="bar" id="b26"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="5" alt="5"/></td><td class="ctr2" id="c26">0%</td><td class="bar" id="d26"/><td class="ctr2" id="e26">n/a</td><td class="ctr1" id="f26">1</td><td class="ctr2" id="g26">1</td><td class="ctr1" id="h26">1</td><td class="ctr2" id="i26">1</td><td class="ctr1" id="j26">1</td><td class="ctr2" id="k26">1</td></tr><tr><td id="a26"><a href="GeneralWebController.java.html#L36" class="el_method">static {...}</a></td><td class="bar" id="b27"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c27">0%</td><td class="bar" id="d27"/><td class="ctr2" id="e27">n/a</td><td class="ctr1" id="f27">1</td><td class="ctr2" id="g27">1</td><td class="ctr1" id="h27">1</td><td class="ctr2" id="i27">1</td><td class="ctr1" id="j27">1</td><td class="ctr2" id="k27">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController.java.html b/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController.java.html
    new file mode 100644
    index 000000000..695b60560
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/GeneralWebController.java.html
    @@ -0,0 +1,347 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GeneralWebController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">GeneralWebController.java</span></div><h1>GeneralWebController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.charset.StandardCharsets;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.util.*;
    +import java.util.stream.Stream;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.core.io.Resource;
    +import org.springframework.core.io.ResourceLoader;
    +import org.springframework.stereotype.Controller;
    +import org.springframework.ui.Model;
    +import org.springframework.web.bind.annotation.GetMapping;
    +
    +import com.fasterxml.jackson.core.type.TypeReference;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.config.RuntimePathConfig;
    +import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
    +import stirling.software.SPDF.model.SignatureFile;
    +import stirling.software.SPDF.service.SignatureService;
    +import stirling.software.SPDF.utils.GeneralUtils;
    +
    +@Controller
    +@Tag(name = &quot;General&quot;, description = &quot;General APIs&quot;)
    +<span class="nc" id="L36">@Slf4j</span>
    +public class GeneralWebController {
    +
    +    private final SignatureService signatureService;
    +    private final UserServiceInterface userService;
    +    private final ResourceLoader resourceLoader;
    +    private final RuntimePathConfig runtimePathConfig;
    +
    +    public GeneralWebController(
    +            SignatureService signatureService,
    +            @Autowired(required = false) UserServiceInterface userService,
    +            ResourceLoader resourceLoader,
    +<span class="nc" id="L48">            RuntimePathConfig runtimePathConfig) {</span>
    +<span class="nc" id="L49">        this.signatureService = signatureService;</span>
    +<span class="nc" id="L50">        this.userService = userService;</span>
    +<span class="nc" id="L51">        this.resourceLoader = resourceLoader;</span>
    +<span class="nc" id="L52">        this.runtimePathConfig = runtimePathConfig;</span>
    +<span class="nc" id="L53">    }</span>
    +
    +    @GetMapping(&quot;/pipeline&quot;)
    +    @Hidden
    +    public String pipelineForm(Model model) {
    +<span class="nc" id="L58">        model.addAttribute(&quot;currentPage&quot;, &quot;pipeline&quot;);</span>
    +<span class="nc" id="L59">        List&lt;String&gt; pipelineConfigs = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L60">        List&lt;Map&lt;String, String&gt;&gt; pipelineConfigsWithNames = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        if (new File(runtimePathConfig.getPipelineDefaultWebUiConfigs()).exists()) {</span>
    +<span class="nc" id="L62">            try (Stream&lt;Path&gt; paths =</span>
    +<span class="nc" id="L63">                    Files.walk(Paths.get(runtimePathConfig.getPipelineDefaultWebUiConfigs()))) {</span>
    +<span class="nc" id="L64">                List&lt;Path&gt; jsonFiles =</span>
    +<span class="nc" id="L65">                        paths.filter(Files::isRegularFile)</span>
    +<span class="nc" id="L66">                                .filter(p -&gt; p.toString().endsWith(&quot;.json&quot;))</span>
    +<span class="nc" id="L67">                                .toList();</span>
    +<span class="nc bnc" id="L68" title="All 2 branches missed.">                for (Path jsonFile : jsonFiles) {</span>
    +<span class="nc" id="L69">                    String content = Files.readString(jsonFile, StandardCharsets.UTF_8);</span>
    +<span class="nc" id="L70">                    pipelineConfigs.add(content);</span>
    +<span class="nc" id="L71">                }</span>
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">                for (String config : pipelineConfigs) {</span>
    +<span class="nc" id="L73">                    Map&lt;String, Object&gt; jsonContent =</span>
    +                            new ObjectMapper()
    +<span class="nc" id="L75">                                    .readValue(config, new TypeReference&lt;Map&lt;String, Object&gt;&gt;() {});</span>
    +<span class="nc" id="L76">                    String name = (String) jsonContent.get(&quot;name&quot;);</span>
    +<span class="nc bnc" id="L77" title="All 4 branches missed.">                    if (name == null || name.length() &lt; 1) {</span>
    +<span class="nc" id="L78">                        String filename =</span>
    +                                jsonFiles
    +<span class="nc" id="L80">                                        .get(pipelineConfigs.indexOf(config))</span>
    +<span class="nc" id="L81">                                        .getFileName()</span>
    +<span class="nc" id="L82">                                        .toString();</span>
    +<span class="nc" id="L83">                        name = filename.substring(0, filename.lastIndexOf('.'));</span>
    +                    }
    +<span class="nc" id="L85">                    Map&lt;String, String&gt; configWithName = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L86">                    configWithName.put(&quot;json&quot;, config);</span>
    +<span class="nc" id="L87">                    configWithName.put(&quot;name&quot;, name);</span>
    +<span class="nc" id="L88">                    pipelineConfigsWithNames.add(configWithName);</span>
    +<span class="nc" id="L89">                }</span>
    +<span class="nc" id="L90">            } catch (IOException e) {</span>
    +<span class="nc" id="L91">                log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L92">            }</span>
    +        }
    +<span class="nc bnc" id="L94" title="All 2 branches missed.">        if (pipelineConfigsWithNames.size() == 0) {</span>
    +<span class="nc" id="L95">            Map&lt;String, String&gt; configWithName = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L96">            configWithName.put(&quot;json&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L97">            configWithName.put(&quot;name&quot;, &quot;No preloaded configs found&quot;);</span>
    +<span class="nc" id="L98">            pipelineConfigsWithNames.add(configWithName);</span>
    +        }
    +<span class="nc" id="L100">        model.addAttribute(&quot;pipelineConfigsWithNames&quot;, pipelineConfigsWithNames);</span>
    +<span class="nc" id="L101">        model.addAttribute(&quot;pipelineConfigs&quot;, pipelineConfigs);</span>
    +<span class="nc" id="L102">        return &quot;pipeline&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/merge-pdfs&quot;)
    +    @Hidden
    +    public String mergePdfForm(Model model) {
    +<span class="nc" id="L108">        model.addAttribute(&quot;currentPage&quot;, &quot;merge-pdfs&quot;);</span>
    +<span class="nc" id="L109">        return &quot;merge-pdfs&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/split-pdf-by-sections&quot;)
    +    @Hidden
    +    public String splitPdfBySections(Model model) {
    +<span class="nc" id="L115">        model.addAttribute(&quot;currentPage&quot;, &quot;split-pdf-by-sections&quot;);</span>
    +<span class="nc" id="L116">        return &quot;split-pdf-by-sections&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/split-pdf-by-chapters&quot;)
    +    @Hidden
    +    public String splitPdfByChapters(Model model) {
    +<span class="nc" id="L122">        model.addAttribute(&quot;currentPage&quot;, &quot;split-pdf-by-chapters&quot;);</span>
    +<span class="nc" id="L123">        return &quot;split-pdf-by-chapters&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/view-pdf&quot;)
    +    @Hidden
    +    public String ViewPdfForm2(Model model) {
    +<span class="nc" id="L129">        model.addAttribute(&quot;currentPage&quot;, &quot;view-pdf&quot;);</span>
    +<span class="nc" id="L130">        return &quot;view-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/multi-tool&quot;)
    +    @Hidden
    +    public String multiToolForm(Model model) {
    +<span class="nc" id="L136">        model.addAttribute(&quot;currentPage&quot;, &quot;multi-tool&quot;);</span>
    +<span class="nc" id="L137">        return &quot;multi-tool&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/remove-pages&quot;)
    +    @Hidden
    +    public String pageDeleter(Model model) {
    +<span class="nc" id="L143">        model.addAttribute(&quot;currentPage&quot;, &quot;remove-pages&quot;);</span>
    +<span class="nc" id="L144">        return &quot;remove-pages&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-organizer&quot;)
    +    @Hidden
    +    public String pageOrganizer(Model model) {
    +<span class="nc" id="L150">        model.addAttribute(&quot;currentPage&quot;, &quot;pdf-organizer&quot;);</span>
    +<span class="nc" id="L151">        return &quot;pdf-organizer&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/extract-page&quot;)
    +    @Hidden
    +    public String extractPages(Model model) {
    +<span class="nc" id="L157">        model.addAttribute(&quot;currentPage&quot;, &quot;extract-page&quot;);</span>
    +<span class="nc" id="L158">        return &quot;extract-page&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/pdf-to-single-page&quot;)
    +    @Hidden
    +    public String pdfToSinglePage(Model model) {
    +<span class="nc" id="L164">        model.addAttribute(&quot;currentPage&quot;, &quot;pdf-to-single-page&quot;);</span>
    +<span class="nc" id="L165">        return &quot;pdf-to-single-page&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/rotate-pdf&quot;)
    +    @Hidden
    +    public String rotatePdfForm(Model model) {
    +<span class="nc" id="L171">        model.addAttribute(&quot;currentPage&quot;, &quot;rotate-pdf&quot;);</span>
    +<span class="nc" id="L172">        return &quot;rotate-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/split-pdfs&quot;)
    +    @Hidden
    +    public String splitPdfForm(Model model) {
    +<span class="nc" id="L178">        model.addAttribute(&quot;currentPage&quot;, &quot;split-pdfs&quot;);</span>
    +<span class="nc" id="L179">        return &quot;split-pdfs&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/sign&quot;)
    +    @Hidden
    +    public String signForm(Model model) {
    +<span class="nc" id="L185">        String username = &quot;&quot;;</span>
    +<span class="nc bnc" id="L186" title="All 2 branches missed.">        if (userService != null) {</span>
    +<span class="nc" id="L187">            username = userService.getCurrentUsername();</span>
    +        }
    +        // Get signatures from both personal and ALL_USERS folders
    +<span class="nc" id="L190">        List&lt;SignatureFile&gt; signatures = signatureService.getAvailableSignatures(username);</span>
    +<span class="nc" id="L191">        model.addAttribute(&quot;currentPage&quot;, &quot;sign&quot;);</span>
    +<span class="nc" id="L192">        model.addAttribute(&quot;fonts&quot;, getFontNames());</span>
    +<span class="nc" id="L193">        model.addAttribute(&quot;signatures&quot;, signatures);</span>
    +<span class="nc" id="L194">        return &quot;sign&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/multi-page-layout&quot;)
    +    @Hidden
    +    public String multiPageLayoutForm(Model model) {
    +<span class="nc" id="L200">        model.addAttribute(&quot;currentPage&quot;, &quot;multi-page-layout&quot;);</span>
    +<span class="nc" id="L201">        return &quot;multi-page-layout&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/scale-pages&quot;)
    +    @Hidden
    +    public String scalePagesFrom(Model model) {
    +<span class="nc" id="L207">        model.addAttribute(&quot;currentPage&quot;, &quot;scale-pages&quot;);</span>
    +<span class="nc" id="L208">        return &quot;scale-pages&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/split-by-size-or-count&quot;)
    +    @Hidden
    +    public String splitBySizeOrCount(Model model) {
    +<span class="nc" id="L214">        model.addAttribute(&quot;currentPage&quot;, &quot;split-by-size-or-count&quot;);</span>
    +<span class="nc" id="L215">        return &quot;split-by-size-or-count&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/overlay-pdf&quot;)
    +    @Hidden
    +    public String overlayPdf(Model model) {
    +<span class="nc" id="L221">        model.addAttribute(&quot;currentPage&quot;, &quot;overlay-pdf&quot;);</span>
    +<span class="nc" id="L222">        return &quot;overlay-pdf&quot;;</span>
    +    }
    +
    +    private List&lt;FontResource&gt; getFontNames() {
    +<span class="nc" id="L226">        List&lt;FontResource&gt; fontNames = new ArrayList&lt;&gt;();</span>
    +        // Extract font names from classpath
    +<span class="nc" id="L228">        fontNames.addAll(getFontNamesFromLocation(&quot;classpath:static/fonts/*.woff2&quot;));</span>
    +        // Extract font names from external directory
    +<span class="nc" id="L230">        fontNames.addAll(</span>
    +<span class="nc" id="L231">                getFontNamesFromLocation(</span>
    +                        &quot;file:&quot;
    +<span class="nc" id="L233">                                + InstallationPathConfig.getStaticPath()</span>
    +                                + &quot;fonts&quot;
    +                                + File.separator
    +                                + &quot;*&quot;));
    +<span class="nc" id="L237">        return fontNames;</span>
    +    }
    +
    +    private List&lt;FontResource&gt; getFontNamesFromLocation(String locationPattern) {
    +        try {
    +<span class="nc" id="L242">            Resource[] resources =</span>
    +<span class="nc" id="L243">                    GeneralUtils.getResourcesFromLocationPattern(locationPattern, resourceLoader);</span>
    +<span class="nc" id="L244">            return Arrays.stream(resources)</span>
    +<span class="nc" id="L245">                    .map(</span>
    +                            resource -&gt; {
    +                                try {
    +<span class="nc" id="L248">                                    String filename = resource.getFilename();</span>
    +<span class="nc bnc" id="L249" title="All 2 branches missed.">                                    if (filename != null) {</span>
    +<span class="nc" id="L250">                                        int lastDotIndex = filename.lastIndexOf('.');</span>
    +<span class="nc bnc" id="L251" title="All 2 branches missed.">                                        if (lastDotIndex != -1) {</span>
    +<span class="nc" id="L252">                                            String name = filename.substring(0, lastDotIndex);</span>
    +<span class="nc" id="L253">                                            String extension = filename.substring(lastDotIndex + 1);</span>
    +<span class="nc" id="L254">                                            return new FontResource(name, extension);</span>
    +                                        }
    +                                    }
    +<span class="nc" id="L257">                                    return null;</span>
    +<span class="nc" id="L258">                                } catch (Exception e) {</span>
    +<span class="nc" id="L259">                                    throw new RuntimeException(&quot;Error processing filename&quot;, e);</span>
    +                                }
    +                            })
    +<span class="nc" id="L262">                    .filter(Objects::nonNull)</span>
    +<span class="nc" id="L263">                    .toList();</span>
    +<span class="nc" id="L264">        } catch (Exception e) {</span>
    +<span class="nc" id="L265">            throw new RuntimeException(&quot;Failed to read font directory from &quot; + locationPattern, e);</span>
    +        }
    +    }
    +
    +    public String getFormatFromExtension(String extension) {
    +<span class="nc bnc" id="L270" title="All 6 branches missed.">        switch (extension) {</span>
    +            case &quot;ttf&quot;:
    +<span class="nc" id="L272">                return &quot;truetype&quot;;</span>
    +            case &quot;woff&quot;:
    +<span class="nc" id="L274">                return &quot;woff&quot;;</span>
    +            case &quot;woff2&quot;:
    +<span class="nc" id="L276">                return &quot;woff2&quot;;</span>
    +            case &quot;eot&quot;:
    +<span class="nc" id="L278">                return &quot;embedded-opentype&quot;;</span>
    +            case &quot;svg&quot;:
    +<span class="nc" id="L280">                return &quot;svg&quot;;</span>
    +            default:
    +                // or throw an exception if an unexpected extension is encountered
    +<span class="nc" id="L283">                return &quot;&quot;;</span>
    +        }
    +    }
    +
    +    @GetMapping(&quot;/crop&quot;)
    +    @Hidden
    +    public String cropForm(Model model) {
    +<span class="nc" id="L290">        model.addAttribute(&quot;currentPage&quot;, &quot;crop&quot;);</span>
    +<span class="nc" id="L291">        return &quot;crop&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/auto-split-pdf&quot;)
    +    @Hidden
    +    public String autoSPlitPDFForm(Model model) {
    +<span class="nc" id="L297">        model.addAttribute(&quot;currentPage&quot;, &quot;auto-split-pdf&quot;);</span>
    +<span class="nc" id="L298">        return &quot;auto-split-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/remove-image-pdf&quot;)
    +    @Hidden
    +    public String removeImagePdfForm(Model model) {
    +<span class="nc" id="L304">        model.addAttribute(&quot;currentPage&quot;, &quot;remove-image-pdf&quot;);</span>
    +<span class="nc" id="L305">        return &quot;remove-image-pdf&quot;;</span>
    +    }
    +
    +    public class FontResource {
    +
    +        private String name;
    +
    +        private String extension;
    +
    +        private String type;
    +
    +<span class="nc" id="L316">        public FontResource(String name, String extension) {</span>
    +<span class="nc" id="L317">            this.name = name;</span>
    +<span class="nc" id="L318">            this.extension = extension;</span>
    +<span class="nc" id="L319">            this.type = getFormatFromExtension(extension);</span>
    +<span class="nc" id="L320">        }</span>
    +
    +        public String getName() {
    +<span class="nc" id="L323">            return name;</span>
    +        }
    +
    +        public void setName(String name) {
    +<span class="nc" id="L327">            this.name = name;</span>
    +<span class="nc" id="L328">        }</span>
    +
    +        public String getExtension() {
    +<span class="nc" id="L331">            return extension;</span>
    +        }
    +
    +        public void setExtension(String extension) {
    +<span class="nc" id="L335">            this.extension = extension;</span>
    +<span class="nc" id="L336">        }</span>
    +
    +        public String getType() {
    +<span class="nc" id="L339">            return type;</span>
    +        }
    +
    +        public void setType(String type) {
    +<span class="nc" id="L343">            this.type = type;</span>
    +<span class="nc" id="L344">        }</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/HomeWebController$1.html b/testResult/html/stirling.software.SPDF.controller.web/HomeWebController$1.html
    new file mode 100644
    index 000000000..ea117bba0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/HomeWebController$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>HomeWebController.new TypeReference() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">HomeWebController.new TypeReference() {...}</span></div><h1>HomeWebController.new TypeReference() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="HomeWebController.java.html#L54" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/HomeWebController.html b/testResult/html/stirling.software.SPDF.controller.web/HomeWebController.html
    new file mode 100644
    index 000000000..d8f42b9cf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/HomeWebController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>HomeWebController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">HomeWebController</span></div><h1>HomeWebController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">96 of 96</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">28</td><td class="ctr2">28</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a6"><a href="HomeWebController.java.html#L47" class="el_method">licensesForm(Model)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="48" alt="48"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="HomeWebController.java.html#L88" class="el_method">getRobotsTxt()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="13" alt="13"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="HomeWebController.java.html#L40" class="el_method">gameForm(Model)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="7" alt="7"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="HomeWebController.java.html#L69" class="el_method">home(Model)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="7" alt="7"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="HomeWebController.java.html#L80" class="el_method">homeLegacy(Model)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="7" alt="7"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h5">2</td><td class="ctr2" id="i5">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="HomeWebController.java.html#L33" class="el_method">HomeWebController(ApplicationProperties)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="6" alt="6"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a8"><a href="HomeWebController.java.html#L28" class="el_method">static {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a1"><a href="HomeWebController.java.html#L64" class="el_method">getReleaseNotes(Model)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a7"><a href="HomeWebController.java.html#L75" class="el_method">root(Model)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="2" alt="2"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/HomeWebController.java.html b/testResult/html/stirling.software.SPDF.controller.web/HomeWebController.java.html
    new file mode 100644
    index 000000000..9d700e4ca
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/HomeWebController.java.html
    @@ -0,0 +1,96 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>HomeWebController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">HomeWebController.java</span></div><h1>HomeWebController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.nio.charset.StandardCharsets;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.core.io.Resource;
    +import org.springframework.http.MediaType;
    +import org.springframework.stereotype.Controller;
    +import org.springframework.ui.Model;
    +import org.springframework.web.bind.annotation.GetMapping;
    +import org.springframework.web.bind.annotation.ResponseBody;
    +
    +import com.fasterxml.jackson.core.type.TypeReference;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.Dependency;
    +
    +@Controller
    +<span class="nc" id="L28">@Slf4j</span>
    +public class HomeWebController {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L33">    public HomeWebController(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L34">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L35">    }</span>
    +
    +    @GetMapping(&quot;/about&quot;)
    +    @Hidden
    +    public String gameForm(Model model) {
    +<span class="nc" id="L40">        model.addAttribute(&quot;currentPage&quot;, &quot;about&quot;);</span>
    +<span class="nc" id="L41">        return &quot;about&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/licenses&quot;)
    +    @Hidden
    +    public String licensesForm(Model model) {
    +<span class="nc" id="L47">        model.addAttribute(&quot;currentPage&quot;, &quot;licenses&quot;);</span>
    +<span class="nc" id="L48">        Resource resource = new ClassPathResource(&quot;static/3rdPartyLicenses.json&quot;);</span>
    +        try {
    +<span class="nc" id="L50">            InputStream is = resource.getInputStream();</span>
    +<span class="nc" id="L51">            String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);</span>
    +<span class="nc" id="L52">            ObjectMapper mapper = new ObjectMapper();</span>
    +<span class="nc" id="L53">            Map&lt;String, List&lt;Dependency&gt;&gt; data =</span>
    +<span class="nc" id="L54">                    mapper.readValue(json, new TypeReference&lt;Map&lt;String, List&lt;Dependency&gt;&gt;&gt;() {});</span>
    +<span class="nc" id="L55">            model.addAttribute(&quot;dependencies&quot;, data.get(&quot;dependencies&quot;));</span>
    +<span class="nc" id="L56">        } catch (IOException e) {</span>
    +<span class="nc" id="L57">            log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L58">        }</span>
    +<span class="nc" id="L59">        return &quot;licenses&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/releases&quot;)
    +    public String getReleaseNotes(Model model) {
    +<span class="nc" id="L64">        return &quot;releases&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/&quot;)
    +    public String home(Model model) {
    +<span class="nc" id="L69">        model.addAttribute(&quot;currentPage&quot;, &quot;home&quot;);</span>
    +<span class="nc" id="L70">        return &quot;home&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/home&quot;)
    +    public String root(Model model) {
    +<span class="nc" id="L75">        return &quot;redirect:/&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/home-legacy&quot;)
    +    public String homeLegacy(Model model) {
    +<span class="nc" id="L80">        model.addAttribute(&quot;currentPage&quot;, &quot;home-legacy&quot;);</span>
    +<span class="nc" id="L81">        return &quot;home-legacy&quot;;</span>
    +    }
    +
    +    @GetMapping(value = &quot;/robots.txt&quot;, produces = MediaType.TEXT_PLAIN_VALUE)
    +    @ResponseBody
    +    @Hidden
    +    public String getRobotsTxt() {
    +<span class="nc" id="L88">        Boolean allowGoogle = applicationProperties.getSystem().getGooglevisibility();</span>
    +<span class="nc bnc" id="L89" title="All 2 branches missed.">        if (Boolean.TRUE.equals(allowGoogle)) {</span>
    +<span class="nc" id="L90">            return &quot;User-agent: Googlebot\nAllow: /\n\nUser-agent: *\nAllow: /&quot;;</span>
    +        } else {
    +<span class="nc" id="L92">            return &quot;User-agent: Googlebot\nDisallow: /\n\nUser-agent: *\nDisallow: /&quot;;</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/MetricsController$EndpointCount.html b/testResult/html/stirling.software.SPDF.controller.web/MetricsController$EndpointCount.html
    new file mode 100644
    index 000000000..5d82ca10e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/MetricsController$EndpointCount.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsController.EndpointCount</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">MetricsController.EndpointCount</span></div><h1>MetricsController.EndpointCount</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">23 of 23</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a2"><a href="MetricsController.java.html#L378" class="el_method">MetricsController.EndpointCount(String, double)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a4"><a href="MetricsController.java.html#L388" class="el_method">setEndpoint(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="MetricsController.java.html#L396" class="el_method">setCount(double)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="MetricsController.java.html#L384" class="el_method">getEndpoint()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="MetricsController.java.html#L392" class="el_method">getCount()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/MetricsController.html b/testResult/html/stirling.software.SPDF.controller.web/MetricsController.html
    new file mode 100644
    index 000000000..d882af908
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/MetricsController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">MetricsController</span></div><h1>MetricsController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">629 of 629</td><td class="ctr2">0%</td><td class="bar">84 of 84</td><td class="ctr2">0%</td><td class="ctr1">68</td><td class="ctr2">68</td><td class="ctr1">155</td><td class="ctr2">155</td><td class="ctr1">26</td><td class="ctr2">26</td></tr></tfoot><tbody><tr><td id="a19"><a href="MetricsController.java.html#L297" class="el_method">lambda$getUniqueUserCount$3(String, Optional, Counter)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="64" alt="64"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h1">14</td><td class="ctr2" id="i1">14</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a18"><a href="MetricsController.java.html#L216" class="el_method">lambda$getRequestCount$0(String, Optional, Counter)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="64" alt="64"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">11</td><td class="ctr2" id="g1">11</td><td class="ctr1" id="h2">14</td><td class="ctr2" id="i2">14</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a16"><a href="MetricsController.java.html#L257" class="el_method">lambda$getEndpointCounts$1(String, Map, Counter)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="105" height="10" title="56" alt="56"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="16" alt="16"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">9</td><td class="ctr2" id="g2">9</td><td class="ctr1" id="h0">15</td><td class="ctr2" id="i0">15</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="MetricsController.java.html#L365" class="el_method">formatDuration(Duration)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="75" height="10" title="40" alt="40"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h14">5</td><td class="ctr2" id="i14">5</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a8"><a href="MetricsController.java.html#L61" class="el_method">getStatus()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="52" height="10" title="28" alt="28"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="MetricsController.java.html#L250" class="el_method">getEndpointCounts(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="52" height="10" title="28" alt="28"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h3">10</td><td class="ctr2" id="i3">10</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a13"><a href="MetricsController.java.html#L335" class="el_method">getUniqueUserCounts(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="26" alt="26"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h4">10</td><td class="ctr2" id="i4">10</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a12"><a href="MetricsController.java.html#L293" class="el_method">getUniqueUserCount(String, Optional)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="25" alt="25"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h15">5</td><td class="ctr2" id="i15">5</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a22"><a href="MetricsController.java.html#L342" class="el_method">lambda$getUniqueUserCounts$6(Map, Counter)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="23" alt="23"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h16">5</td><td class="ctr2" id="i16">5</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a6"><a href="MetricsController.java.html#L78" class="el_method">getPageLoads(Optional)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="22" alt="22"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h6">6</td><td class="ctr2" id="i6">6</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a10"><a href="MetricsController.java.html#L97" class="el_method">getUniquePageLoads(Optional)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="22" alt="22"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h7">6</td><td class="ctr2" id="i7">6</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a9"><a href="MetricsController.java.html#L149" class="el_method">getTotalRequests(Optional)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="22" alt="22"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h8">6</td><td class="ctr2" id="i8">6</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a11"><a href="MetricsController.java.html#L168" class="el_method">getUniqueTotalRequests(Optional)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="22" alt="22"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h9">6</td><td class="ctr2" id="i9">6</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a1"><a href="MetricsController.java.html#L113" class="el_method">getAllEndpointLoads()</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="20" alt="20"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h10">6</td><td class="ctr2" id="i10">6</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a3"><a href="MetricsController.java.html#L130" class="el_method">getAllUniqueEndpointLoads()</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="20" alt="20"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h11">6</td><td class="ctr2" id="i11">6</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a2"><a href="MetricsController.java.html#L184" class="el_method">getAllPostRequests()</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="20" alt="20"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f11">2</td><td class="ctr2" id="g11">2</td><td class="ctr1" id="h12">6</td><td class="ctr2" id="i12">6</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a4"><a href="MetricsController.java.html#L201" class="el_method">getAllUniquePostRequests()</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="20" alt="20"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f12">2</td><td class="ctr2" id="g12">2</td><td class="ctr1" id="h13">6</td><td class="ctr2" id="i13">6</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a14"><a href="MetricsController.java.html#L356" class="el_method">getUptime()</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="19" alt="19"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f13">2</td><td class="ctr2" id="g13">2</td><td class="ctr1" id="h17">5</td><td class="ctr2" id="i17">5</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a7"><a href="MetricsController.java.html#L213" class="el_method">getRequestCount(String, Optional)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="18" alt="18"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h19">4</td><td class="ctr2" id="i19">4</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a15"><a href="MetricsController.java.html#L50" class="el_method">init()</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="15" alt="15"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f14">2</td><td class="ctr2" id="g14">2</td><td class="ctr1" id="h20">4</td><td class="ctr2" id="i20">4</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a24"><a href="MetricsController.java.html#L42" class="el_method">MetricsController(ApplicationProperties, MeterRegistry, EndpointInspector)</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="12" alt="12"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h18">5</td><td class="ctr2" id="i18">5</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a23"><a href="MetricsController.java.html#L349" class="el_method">lambda$getUniqueUserCounts$7(Map.Entry)</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="12" alt="12"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h22">1</td><td class="ctr2" id="i22">1</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a20"><a href="MetricsController.java.html#L326" class="el_method">lambda$getUniqueUserCount$4(Set, Counter)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="12" alt="12"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f15">2</td><td class="ctr2" id="g15">2</td><td class="ctr1" id="h21">4</td><td class="ctr2" id="i21">4</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a17"><a href="MetricsController.java.html#L287" class="el_method">lambda$getEndpointCounts$2(Map.Entry)</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="11" alt="11"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">1</td><td class="ctr2" id="i23">1</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a21"><a href="MetricsController.java.html#L345" class="el_method">lambda$getUniqueUserCounts$5(String)</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="4" alt="4"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h24">1</td><td class="ctr2" id="i24">1</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr><tr><td id="a25"><a href="MetricsController.java.html#L31" class="el_method">static {...}</a></td><td class="bar" id="b25"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="4" alt="4"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g25">1</td><td class="ctr1" id="h25">1</td><td class="ctr2" id="i25">1</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k25">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/MetricsController.java.html b/testResult/html/stirling.software.SPDF.controller.web/MetricsController.java.html
    new file mode 100644
    index 000000000..92203ff66
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/MetricsController.java.html
    @@ -0,0 +1,400 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">MetricsController.java</span></div><h1>MetricsController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import java.time.Duration;
    +import java.time.LocalDateTime;
    +import java.util.*;
    +
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.GetMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RequestParam;
    +import org.springframework.web.bind.annotation.RestController;
    +
    +import io.micrometer.core.instrument.Counter;
    +import io.micrometer.core.instrument.MeterRegistry;
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.Parameter;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import jakarta.annotation.PostConstruct;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.EndpointInspector;
    +import stirling.software.SPDF.config.StartupApplicationListener;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +@RestController
    +@RequestMapping(&quot;/api/v1/info&quot;)
    +@Tag(name = &quot;Info&quot;, description = &quot;Info APIs&quot;)
    +<span class="nc" id="L31">@Slf4j</span>
    +public class MetricsController {
    +
    +    private final ApplicationProperties applicationProperties;
    +    private final MeterRegistry meterRegistry;
    +    private final EndpointInspector endpointInspector;
    +    private boolean metricsEnabled;
    +
    +    public MetricsController(
    +            ApplicationProperties applicationProperties,
    +            MeterRegistry meterRegistry,
    +<span class="nc" id="L42">            EndpointInspector endpointInspector) {</span>
    +<span class="nc" id="L43">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L44">        this.meterRegistry = meterRegistry;</span>
    +<span class="nc" id="L45">        this.endpointInspector = endpointInspector;</span>
    +<span class="nc" id="L46">    }</span>
    +
    +    @PostConstruct
    +    public void init() {
    +<span class="nc" id="L50">        Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled();</span>
    +<span class="nc bnc" id="L51" title="All 2 branches missed.">        if (metricsEnabled == null) metricsEnabled = true;</span>
    +<span class="nc" id="L52">        this.metricsEnabled = metricsEnabled;</span>
    +<span class="nc" id="L53">    }</span>
    +
    +    @GetMapping(&quot;/status&quot;)
    +    @Operation(
    +            summary = &quot;Application status and version&quot;,
    +            description =
    +                    &quot;This endpoint returns the status of the application and its version number.&quot;)
    +    public ResponseEntity&lt;?&gt; getStatus() {
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L62">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +<span class="nc" id="L64">        Map&lt;String, String&gt; status = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L65">        status.put(&quot;status&quot;, &quot;UP&quot;);</span>
    +<span class="nc" id="L66">        status.put(&quot;version&quot;, getClass().getPackage().getImplementationVersion());</span>
    +<span class="nc" id="L67">        return ResponseEntity.ok(status);</span>
    +    }
    +
    +    @GetMapping(&quot;/load&quot;)
    +    @Operation(
    +            summary = &quot;GET request count&quot;,
    +            description =
    +                    &quot;This endpoint returns the total count of GET requests for a specific endpoint or all endpoints.&quot;)
    +    public ResponseEntity&lt;?&gt; getPageLoads(
    +            @RequestParam(required = false, name = &quot;endpoint&quot;) @Parameter(description = &quot;endpoint&quot;)
    +                    Optional&lt;String&gt; endpoint) {
    +<span class="nc bnc" id="L78" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L79">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L82">            double count = getRequestCount(&quot;GET&quot;, endpoint);</span>
    +<span class="nc" id="L83">            return ResponseEntity.ok(count);</span>
    +<span class="nc" id="L84">        } catch (Exception e) {</span>
    +<span class="nc" id="L85">            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();</span>
    +        }
    +    }
    +
    +    @GetMapping(&quot;/load/unique&quot;)
    +    @Operation(
    +            summary = &quot;Unique users count for GET requests&quot;,
    +            description =
    +                    &quot;This endpoint returns the count of unique users for GET requests for a specific endpoint or all endpoints.&quot;)
    +    public ResponseEntity&lt;?&gt; getUniquePageLoads(
    +            @RequestParam(required = false, name = &quot;endpoint&quot;) @Parameter(description = &quot;endpoint&quot;)
    +                    Optional&lt;String&gt; endpoint) {
    +<span class="nc bnc" id="L97" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L98">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L101">            double count = getUniqueUserCount(&quot;GET&quot;, endpoint);</span>
    +<span class="nc" id="L102">            return ResponseEntity.ok(count);</span>
    +<span class="nc" id="L103">        } catch (Exception e) {</span>
    +<span class="nc" id="L104">            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();</span>
    +        }
    +    }
    +
    +    @GetMapping(&quot;/load/all&quot;)
    +    @Operation(
    +            summary = &quot;GET requests count for all endpoints&quot;,
    +            description = &quot;This endpoint returns the count of GET requests for each endpoint.&quot;)
    +    public ResponseEntity&lt;?&gt; getAllEndpointLoads() {
    +<span class="nc bnc" id="L113" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L114">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L117">            List&lt;EndpointCount&gt; results = getEndpointCounts(&quot;GET&quot;);</span>
    +<span class="nc" id="L118">            return ResponseEntity.ok(results);</span>
    +<span class="nc" id="L119">        } catch (Exception e) {</span>
    +<span class="nc" id="L120">            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();</span>
    +        }
    +    }
    +
    +    @GetMapping(&quot;/load/all/unique&quot;)
    +    @Operation(
    +            summary = &quot;Unique users count for GET requests for all endpoints&quot;,
    +            description =
    +                    &quot;This endpoint returns the count of unique users for GET requests for each endpoint.&quot;)
    +    public ResponseEntity&lt;?&gt; getAllUniqueEndpointLoads() {
    +<span class="nc bnc" id="L130" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L131">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L134">            List&lt;EndpointCount&gt; results = getUniqueUserCounts(&quot;GET&quot;);</span>
    +<span class="nc" id="L135">            return ResponseEntity.ok(results);</span>
    +<span class="nc" id="L136">        } catch (Exception e) {</span>
    +<span class="nc" id="L137">            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();</span>
    +        }
    +    }
    +
    +    @GetMapping(&quot;/requests&quot;)
    +    @Operation(
    +            summary = &quot;POST request count&quot;,
    +            description =
    +                    &quot;This endpoint returns the total count of POST requests for a specific endpoint or all endpoints.&quot;)
    +    public ResponseEntity&lt;?&gt; getTotalRequests(
    +            @RequestParam(required = false, name = &quot;endpoint&quot;) @Parameter(description = &quot;endpoint&quot;)
    +                    Optional&lt;String&gt; endpoint) {
    +<span class="nc bnc" id="L149" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L150">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L153">            double count = getRequestCount(&quot;POST&quot;, endpoint);</span>
    +<span class="nc" id="L154">            return ResponseEntity.ok(count);</span>
    +<span class="nc" id="L155">        } catch (Exception e) {</span>
    +<span class="nc" id="L156">            return ResponseEntity.ok(-1);</span>
    +        }
    +    }
    +
    +    @GetMapping(&quot;/requests/unique&quot;)
    +    @Operation(
    +            summary = &quot;Unique users count for POST requests&quot;,
    +            description =
    +                    &quot;This endpoint returns the count of unique users for POST requests for a specific endpoint or all endpoints.&quot;)
    +    public ResponseEntity&lt;?&gt; getUniqueTotalRequests(
    +            @RequestParam(required = false, name = &quot;endpoint&quot;) @Parameter(description = &quot;endpoint&quot;)
    +                    Optional&lt;String&gt; endpoint) {
    +<span class="nc bnc" id="L168" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L169">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L172">            double count = getUniqueUserCount(&quot;POST&quot;, endpoint);</span>
    +<span class="nc" id="L173">            return ResponseEntity.ok(count);</span>
    +<span class="nc" id="L174">        } catch (Exception e) {</span>
    +<span class="nc" id="L175">            return ResponseEntity.ok(-1);</span>
    +        }
    +    }
    +
    +    @GetMapping(&quot;/requests/all&quot;)
    +    @Operation(
    +            summary = &quot;POST requests count for all endpoints&quot;,
    +            description = &quot;This endpoint returns the count of POST requests for each endpoint.&quot;)
    +    public ResponseEntity&lt;?&gt; getAllPostRequests() {
    +<span class="nc bnc" id="L184" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L185">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L188">            List&lt;EndpointCount&gt; results = getEndpointCounts(&quot;POST&quot;);</span>
    +<span class="nc" id="L189">            return ResponseEntity.ok(results);</span>
    +<span class="nc" id="L190">        } catch (Exception e) {</span>
    +<span class="nc" id="L191">            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();</span>
    +        }
    +    }
    +
    +    @GetMapping(&quot;/requests/all/unique&quot;)
    +    @Operation(
    +            summary = &quot;Unique users count for POST requests for all endpoints&quot;,
    +            description =
    +                    &quot;This endpoint returns the count of unique users for POST requests for each endpoint.&quot;)
    +    public ResponseEntity&lt;?&gt; getAllUniquePostRequests() {
    +<span class="nc bnc" id="L201" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L202">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +        try {
    +<span class="nc" id="L205">            List&lt;EndpointCount&gt; results = getUniqueUserCounts(&quot;POST&quot;);</span>
    +<span class="nc" id="L206">            return ResponseEntity.ok(results);</span>
    +<span class="nc" id="L207">        } catch (Exception e) {</span>
    +<span class="nc" id="L208">            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();</span>
    +        }
    +    }
    +
    +    private double getRequestCount(String method, Optional&lt;String&gt; endpoint) {
    +<span class="nc" id="L213">        return meterRegistry.find(&quot;http.requests&quot;).tag(&quot;method&quot;, method).counters().stream()</span>
    +<span class="nc" id="L214">                .filter(</span>
    +                        counter -&gt; {
    +<span class="nc" id="L216">                            String uri = counter.getId().getTag(&quot;uri&quot;);</span>
    +
    +                            // Apply filtering logic - Skip if uri is null
    +<span class="nc bnc" id="L219" title="All 2 branches missed.">                            if (uri == null) {</span>
    +<span class="nc" id="L220">                                return false;</span>
    +                            }
    +
    +                            // For POST requests, only include if they start with /api/v1
    +<span class="nc bnc" id="L224" title="All 4 branches missed.">                            if (&quot;POST&quot;.equals(method) &amp;&amp; !uri.contains(&quot;api/v1&quot;)) {</span>
    +<span class="nc" id="L225">                                return false;</span>
    +                            }
    +
    +<span class="nc bnc" id="L228" title="All 2 branches missed.">                            if (uri.contains(&quot;.txt&quot;)) {</span>
    +<span class="nc" id="L229">                                return false;</span>
    +                            }
    +
    +                            // For GET requests, validate if we have a list of valid endpoints
    +<span class="nc" id="L233">                            final boolean validateGetEndpoints =</span>
    +<span class="nc bnc" id="L234" title="All 2 branches missed.">                                    endpointInspector.getValidGetEndpoints().size() != 0;</span>
    +<span class="nc bnc" id="L235" title="All 4 branches missed.">                            if (&quot;GET&quot;.equals(method)</span>
    +                                    &amp;&amp; validateGetEndpoints
    +<span class="nc bnc" id="L237" title="All 2 branches missed.">                                    &amp;&amp; !endpointInspector.isValidGetEndpoint(uri)) {</span>
    +<span class="nc" id="L238">                                log.debug(&quot;Skipping invalid GET endpoint: {}&quot;, uri);</span>
    +<span class="nc" id="L239">                                return false;</span>
    +                            }
    +
    +                            // Filter for specific endpoint if provided
    +<span class="nc bnc" id="L243" title="All 4 branches missed.">                            return !endpoint.isPresent() || endpoint.get().equals(uri);</span>
    +                        })
    +<span class="nc" id="L245">                .mapToDouble(Counter::count)</span>
    +<span class="nc" id="L246">                .sum();</span>
    +    }
    +
    +    private List&lt;EndpointCount&gt; getEndpointCounts(String method) {
    +<span class="nc" id="L250">        Map&lt;String, Double&gt; counts = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L251">        meterRegistry</span>
    +<span class="nc" id="L252">                .find(&quot;http.requests&quot;)</span>
    +<span class="nc" id="L253">                .tag(&quot;method&quot;, method)</span>
    +<span class="nc" id="L254">                .counters()</span>
    +<span class="nc" id="L255">                .forEach(</span>
    +                        counter -&gt; {
    +<span class="nc" id="L257">                            String uri = counter.getId().getTag(&quot;uri&quot;);</span>
    +
    +                            // Skip if uri is null
    +<span class="nc bnc" id="L260" title="All 2 branches missed.">                            if (uri == null) {</span>
    +<span class="nc" id="L261">                                return;</span>
    +                            }
    +
    +                            // For POST requests, only include if they start with /api/v1
    +<span class="nc bnc" id="L265" title="All 4 branches missed.">                            if (&quot;POST&quot;.equals(method) &amp;&amp; !uri.contains(&quot;api/v1&quot;)) {</span>
    +<span class="nc" id="L266">                                return;</span>
    +                            }
    +
    +<span class="nc bnc" id="L269" title="All 2 branches missed.">                            if (uri.contains(&quot;.txt&quot;)) {</span>
    +<span class="nc" id="L270">                                return;</span>
    +                            }
    +
    +                            // For GET requests, validate if we have a list of valid endpoints
    +<span class="nc" id="L274">                            final boolean validateGetEndpoints =</span>
    +<span class="nc bnc" id="L275" title="All 2 branches missed.">                                    endpointInspector.getValidGetEndpoints().size() != 0;</span>
    +<span class="nc bnc" id="L276" title="All 4 branches missed.">                            if (&quot;GET&quot;.equals(method)</span>
    +                                    &amp;&amp; validateGetEndpoints
    +<span class="nc bnc" id="L278" title="All 2 branches missed.">                                    &amp;&amp; !endpointInspector.isValidGetEndpoint(uri)) {</span>
    +<span class="nc" id="L279">                                log.debug(&quot;Skipping invalid GET endpoint: {}&quot;, uri);</span>
    +<span class="nc" id="L280">                                return;</span>
    +                            }
    +
    +<span class="nc" id="L283">                            counts.merge(uri, counter.count(), Double::sum);</span>
    +<span class="nc" id="L284">                        });</span>
    +
    +<span class="nc" id="L286">        return counts.entrySet().stream()</span>
    +<span class="nc" id="L287">                .map(entry -&gt; new EndpointCount(entry.getKey(), entry.getValue()))</span>
    +<span class="nc" id="L288">                .sorted(Comparator.comparing(EndpointCount::getCount).reversed())</span>
    +<span class="nc" id="L289">                .toList();</span>
    +    }
    +
    +    private double getUniqueUserCount(String method, Optional&lt;String&gt; endpoint) {
    +<span class="nc" id="L293">        Set&lt;String&gt; uniqueUsers = new HashSet&lt;&gt;();</span>
    +<span class="nc" id="L294">        meterRegistry.find(&quot;http.requests&quot;).tag(&quot;method&quot;, method).counters().stream()</span>
    +<span class="nc" id="L295">                .filter(</span>
    +                        counter -&gt; {
    +<span class="nc" id="L297">                            String uri = counter.getId().getTag(&quot;uri&quot;);</span>
    +
    +                            // Skip if uri is null
    +<span class="nc bnc" id="L300" title="All 2 branches missed.">                            if (uri == null) {</span>
    +<span class="nc" id="L301">                                return false;</span>
    +                            }
    +
    +                            // For POST requests, only include if they start with /api/v1
    +<span class="nc bnc" id="L305" title="All 4 branches missed.">                            if (&quot;POST&quot;.equals(method) &amp;&amp; !uri.contains(&quot;api/v1&quot;)) {</span>
    +<span class="nc" id="L306">                                return false;</span>
    +                            }
    +
    +<span class="nc bnc" id="L309" title="All 2 branches missed.">                            if (uri.contains(&quot;.txt&quot;)) {</span>
    +<span class="nc" id="L310">                                return false;</span>
    +                            }
    +
    +                            // For GET requests, validate if we have a list of valid endpoints
    +<span class="nc" id="L314">                            final boolean validateGetEndpoints =</span>
    +<span class="nc bnc" id="L315" title="All 2 branches missed.">                                    endpointInspector.getValidGetEndpoints().size() != 0;</span>
    +<span class="nc bnc" id="L316" title="All 4 branches missed.">                            if (&quot;GET&quot;.equals(method)</span>
    +                                    &amp;&amp; validateGetEndpoints
    +<span class="nc bnc" id="L318" title="All 2 branches missed.">                                    &amp;&amp; !endpointInspector.isValidGetEndpoint(uri)) {</span>
    +<span class="nc" id="L319">                                log.debug(&quot;Skipping invalid GET endpoint: {}&quot;, uri);</span>
    +<span class="nc" id="L320">                                return false;</span>
    +                            }
    +<span class="nc bnc" id="L322" title="All 4 branches missed.">                            return !endpoint.isPresent() || endpoint.get().equals(uri);</span>
    +                        })
    +<span class="nc" id="L324">                .forEach(</span>
    +                        counter -&gt; {
    +<span class="nc" id="L326">                            String session = counter.getId().getTag(&quot;session&quot;);</span>
    +<span class="nc bnc" id="L327" title="All 2 branches missed.">                            if (session != null) {</span>
    +<span class="nc" id="L328">                                uniqueUsers.add(session);</span>
    +                            }
    +<span class="nc" id="L330">                        });</span>
    +<span class="nc" id="L331">        return uniqueUsers.size();</span>
    +    }
    +
    +    private List&lt;EndpointCount&gt; getUniqueUserCounts(String method) {
    +<span class="nc" id="L335">        Map&lt;String, Set&lt;String&gt;&gt; uniqueUsers = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L336">        meterRegistry</span>
    +<span class="nc" id="L337">                .find(&quot;http.requests&quot;)</span>
    +<span class="nc" id="L338">                .tag(&quot;method&quot;, method)</span>
    +<span class="nc" id="L339">                .counters()</span>
    +<span class="nc" id="L340">                .forEach(</span>
    +                        counter -&gt; {
    +<span class="nc" id="L342">                            String uri = counter.getId().getTag(&quot;uri&quot;);</span>
    +<span class="nc" id="L343">                            String session = counter.getId().getTag(&quot;session&quot;);</span>
    +<span class="nc bnc" id="L344" title="All 4 branches missed.">                            if (uri != null &amp;&amp; session != null) {</span>
    +<span class="nc" id="L345">                                uniqueUsers.computeIfAbsent(uri, k -&gt; new HashSet&lt;&gt;()).add(session);</span>
    +                            }
    +<span class="nc" id="L347">                        });</span>
    +<span class="nc" id="L348">        return uniqueUsers.entrySet().stream()</span>
    +<span class="nc" id="L349">                .map(entry -&gt; new EndpointCount(entry.getKey(), entry.getValue().size()))</span>
    +<span class="nc" id="L350">                .sorted(Comparator.comparing(EndpointCount::getCount).reversed())</span>
    +<span class="nc" id="L351">                .toList();</span>
    +    }
    +
    +    @GetMapping(&quot;/uptime&quot;)
    +    public ResponseEntity&lt;?&gt; getUptime() {
    +<span class="nc bnc" id="L356" title="All 2 branches missed.">        if (!metricsEnabled) {</span>
    +<span class="nc" id="L357">            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(&quot;This endpoint is disabled.&quot;);</span>
    +        }
    +<span class="nc" id="L359">        LocalDateTime now = LocalDateTime.now();</span>
    +<span class="nc" id="L360">        Duration uptime = Duration.between(StartupApplicationListener.startTime, now);</span>
    +<span class="nc" id="L361">        return ResponseEntity.ok(formatDuration(uptime));</span>
    +    }
    +
    +    private String formatDuration(Duration duration) {
    +<span class="nc" id="L365">        long days = duration.toDays();</span>
    +<span class="nc" id="L366">        long hours = duration.toHoursPart();</span>
    +<span class="nc" id="L367">        long minutes = duration.toMinutesPart();</span>
    +<span class="nc" id="L368">        long seconds = duration.toSecondsPart();</span>
    +<span class="nc" id="L369">        return String.format(&quot;%dd %dh %dm %ds&quot;, days, hours, minutes, seconds);</span>
    +    }
    +
    +    public static class EndpointCount {
    +
    +        private String endpoint;
    +
    +        private double count;
    +
    +<span class="nc" id="L378">        public EndpointCount(String endpoint, double count) {</span>
    +<span class="nc" id="L379">            this.endpoint = endpoint;</span>
    +<span class="nc" id="L380">            this.count = count;</span>
    +<span class="nc" id="L381">        }</span>
    +
    +        public String getEndpoint() {
    +<span class="nc" id="L384">            return endpoint;</span>
    +        }
    +
    +        public void setEndpoint(String endpoint) {
    +<span class="nc" id="L388">            this.endpoint = endpoint;</span>
    +<span class="nc" id="L389">        }</span>
    +
    +        public double getCount() {
    +<span class="nc" id="L392">            return count;</span>
    +        }
    +
    +        public void setCount(double count) {
    +<span class="nc" id="L396">            this.count = count;</span>
    +<span class="nc" id="L397">        }</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/OtherWebController.html b/testResult/html/stirling.software.SPDF.controller.web/OtherWebController.html
    new file mode 100644
    index 000000000..4eabad284
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/OtherWebController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OtherWebController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">OtherWebController</span></div><h1>OtherWebController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">217 of 217</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">27</td><td class="ctr2">27</td><td class="ctr1">59</td><td class="ctr2">59</td><td class="ctr1">25</td><td class="ctr2">25</td></tr></tfoot><tbody><tr><td id="a12"><a href="OtherWebController.java.html#L117" class="el_method">getAvailableTesseractLanguages()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="26" alt="26"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">10</td><td class="ctr2" id="i0">10</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a7"><a href="OtherWebController.java.html#L46" class="el_method">extractImageScansForm()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="92" height="10" title="20" alt="20"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a16"><a href="OtherWebController.java.html#L133" class="el_method">ocrPdfPage()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="92" height="10" title="20" alt="20"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a15"><a href="OtherWebController.java.html#L125" class="el_method">lambda$getAvailableTesseractLanguages$2(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="8" alt="8"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h22">1</td><td class="ctr2" id="i22">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="OtherWebController.java.html#L32" class="el_method">compressPdfForm(Model)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a23"><a href="OtherWebController.java.html#L39" class="el_method">replaceAndInvertColorPdfForm(Model)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">2</td><td class="ctr2" id="i5">2</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a9"><a href="OtherWebController.java.html#L56" class="el_method">extractJavascriptForm(Model)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">2</td><td class="ctr2" id="i6">2</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a24"><a href="OtherWebController.java.html#L63" class="el_method">stampForm(Model)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">2</td><td class="ctr2" id="i7">2</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a0"><a href="OtherWebController.java.html#L70" class="el_method">addPageNumbersForm(Model)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a10"><a href="OtherWebController.java.html#L77" class="el_method">fakeScanForm(Model)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a8"><a href="OtherWebController.java.html#L84" class="el_method">extractImagesForm(Model)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">2</td><td class="ctr2" id="i10">2</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a11"><a href="OtherWebController.java.html#L91" class="el_method">flattenForm(Model)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">2</td><td class="ctr2" id="i11">2</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a1"><a href="OtherWebController.java.html#L98" class="el_method">addWatermarkForm(Model)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h12">2</td><td class="ctr2" id="i12">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a4"><a href="OtherWebController.java.html#L105" class="el_method">compareForm(Model)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">2</td><td class="ctr2" id="i13">2</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a19"><a href="OtherWebController.java.html#L112" class="el_method">printFileForm(Model)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h14">2</td><td class="ctr2" id="i14">2</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a18"><a href="OtherWebController.java.html#L143" class="el_method">overlayImage(Model)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h15">2</td><td class="ctr2" id="i15">2</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a6"><a href="OtherWebController.java.html#L150" class="el_method">contrast(Model)</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h16">2</td><td class="ctr2" id="i16">2</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a22"><a href="OtherWebController.java.html#L157" class="el_method">repairForm(Model)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h17">2</td><td class="ctr2" id="i17">2</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a21"><a href="OtherWebController.java.html#L164" class="el_method">removeBlanksForm(Model)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h18">2</td><td class="ctr2" id="i18">2</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a20"><a href="OtherWebController.java.html#L171" class="el_method">removeAnnotationsForm(Model)</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h19">2</td><td class="ctr2" id="i19">2</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a2"><a href="OtherWebController.java.html#L178" class="el_method">autoCropForm(Model)</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">2</td><td class="ctr2" id="i20">2</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a3"><a href="OtherWebController.java.html#L185" class="el_method">autoRenameForm(Model)</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">2</td><td class="ctr2" id="i21">2</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a17"><a href="OtherWebController.java.html#L25" class="el_method">OtherWebController(ApplicationProperties)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="6" alt="6"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a14"><a href="OtherWebController.java.html#L124" class="el_method">lambda$getAvailableTesseractLanguages$1(File)</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="6" alt="6"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">1</td><td class="ctr2" id="i23">1</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a13"><a href="OtherWebController.java.html#L123" class="el_method">lambda$getAvailableTesseractLanguages$0(File)</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="5" alt="5"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h24">1</td><td class="ctr2" id="i24">1</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/OtherWebController.java.html b/testResult/html/stirling.software.SPDF.controller.web/OtherWebController.java.html
    new file mode 100644
    index 000000000..1a1b49482
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/OtherWebController.java.html
    @@ -0,0 +1,189 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OtherWebController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">OtherWebController.java</span></div><h1>OtherWebController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import java.io.File;
    +import java.util.Arrays;
    +import java.util.Collections;
    +import java.util.List;
    +
    +import org.springframework.stereotype.Controller;
    +import org.springframework.ui.Model;
    +import org.springframework.web.bind.annotation.GetMapping;
    +import org.springframework.web.servlet.ModelAndView;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.utils.CheckProgramInstall;
    +
    +@Controller
    +@Tag(name = &quot;Misc&quot;, description = &quot;Miscellaneous APIs&quot;)
    +public class OtherWebController {
    +
    +    private final ApplicationProperties applicationProperties;
    +
    +<span class="nc" id="L25">    public OtherWebController(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L26">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L27">    }</span>
    +
    +    @GetMapping(&quot;/compress-pdf&quot;)
    +    @Hidden
    +    public String compressPdfForm(Model model) {
    +<span class="nc" id="L32">        model.addAttribute(&quot;currentPage&quot;, &quot;compress-pdf&quot;);</span>
    +<span class="nc" id="L33">        return &quot;misc/compress-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/replace-and-invert-color-pdf&quot;)
    +    @Hidden
    +    public String replaceAndInvertColorPdfForm(Model model) {
    +<span class="nc" id="L39">        model.addAttribute(&quot;currentPage&quot;, &quot;replace-invert-color-pdf&quot;);</span>
    +<span class="nc" id="L40">        return &quot;misc/replace-color&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/extract-image-scans&quot;)
    +    @Hidden
    +    public ModelAndView extractImageScansForm() {
    +<span class="nc" id="L46">        ModelAndView modelAndView = new ModelAndView(&quot;misc/extract-image-scans&quot;);</span>
    +<span class="nc" id="L47">        boolean isPython = CheckProgramInstall.isPythonAvailable();</span>
    +<span class="nc" id="L48">        modelAndView.addObject(&quot;isPython&quot;, isPython);</span>
    +<span class="nc" id="L49">        modelAndView.addObject(&quot;currentPage&quot;, &quot;extract-image-scans&quot;);</span>
    +<span class="nc" id="L50">        return modelAndView;</span>
    +    }
    +
    +    @GetMapping(&quot;/show-javascript&quot;)
    +    @Hidden
    +    public String extractJavascriptForm(Model model) {
    +<span class="nc" id="L56">        model.addAttribute(&quot;currentPage&quot;, &quot;show-javascript&quot;);</span>
    +<span class="nc" id="L57">        return &quot;misc/show-javascript&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/stamp&quot;)
    +    @Hidden
    +    public String stampForm(Model model) {
    +<span class="nc" id="L63">        model.addAttribute(&quot;currentPage&quot;, &quot;stamp&quot;);</span>
    +<span class="nc" id="L64">        return &quot;misc/stamp&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/add-page-numbers&quot;)
    +    @Hidden
    +    public String addPageNumbersForm(Model model) {
    +<span class="nc" id="L70">        model.addAttribute(&quot;currentPage&quot;, &quot;add-page-numbers&quot;);</span>
    +<span class="nc" id="L71">        return &quot;misc/add-page-numbers&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/fake-scan&quot;)
    +    @Hidden
    +    public String fakeScanForm(Model model) {
    +<span class="nc" id="L77">        model.addAttribute(&quot;currentPage&quot;, &quot;fake-scan&quot;);</span>
    +<span class="nc" id="L78">        return &quot;misc/fake-scan&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/extract-images&quot;)
    +    @Hidden
    +    public String extractImagesForm(Model model) {
    +<span class="nc" id="L84">        model.addAttribute(&quot;currentPage&quot;, &quot;extract-images&quot;);</span>
    +<span class="nc" id="L85">        return &quot;misc/extract-images&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/flatten&quot;)
    +    @Hidden
    +    public String flattenForm(Model model) {
    +<span class="nc" id="L91">        model.addAttribute(&quot;currentPage&quot;, &quot;flatten&quot;);</span>
    +<span class="nc" id="L92">        return &quot;misc/flatten&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/change-metadata&quot;)
    +    @Hidden
    +    public String addWatermarkForm(Model model) {
    +<span class="nc" id="L98">        model.addAttribute(&quot;currentPage&quot;, &quot;change-metadata&quot;);</span>
    +<span class="nc" id="L99">        return &quot;misc/change-metadata&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/compare&quot;)
    +    @Hidden
    +    public String compareForm(Model model) {
    +<span class="nc" id="L105">        model.addAttribute(&quot;currentPage&quot;, &quot;compare&quot;);</span>
    +<span class="nc" id="L106">        return &quot;misc/compare&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/print-file&quot;)
    +    @Hidden
    +    public String printFileForm(Model model) {
    +<span class="nc" id="L112">        model.addAttribute(&quot;currentPage&quot;, &quot;print-file&quot;);</span>
    +<span class="nc" id="L113">        return &quot;misc/print-file&quot;;</span>
    +    }
    +
    +    public List&lt;String&gt; getAvailableTesseractLanguages() {
    +<span class="nc" id="L117">        String tessdataDir = applicationProperties.getSystem().getTessdataDir();</span>
    +<span class="nc" id="L118">        File[] files = new File(tessdataDir).listFiles();</span>
    +<span class="nc bnc" id="L119" title="All 2 branches missed.">        if (files == null) {</span>
    +<span class="nc" id="L120">            return Collections.emptyList();</span>
    +        }
    +<span class="nc" id="L122">        return Arrays.stream(files)</span>
    +<span class="nc" id="L123">                .filter(file -&gt; file.getName().endsWith(&quot;.traineddata&quot;))</span>
    +<span class="nc" id="L124">                .map(file -&gt; file.getName().replace(&quot;.traineddata&quot;, &quot;&quot;))</span>
    +<span class="nc bnc" id="L125" title="All 2 branches missed.">                .filter(lang -&gt; !lang.equalsIgnoreCase(&quot;osd&quot;))</span>
    +<span class="nc" id="L126">                .sorted()</span>
    +<span class="nc" id="L127">                .toList();</span>
    +    }
    +
    +    @GetMapping(&quot;/ocr-pdf&quot;)
    +    @Hidden
    +    public ModelAndView ocrPdfPage() {
    +<span class="nc" id="L133">        ModelAndView modelAndView = new ModelAndView(&quot;misc/ocr-pdf&quot;);</span>
    +<span class="nc" id="L134">        List&lt;String&gt; languages = getAvailableTesseractLanguages();</span>
    +<span class="nc" id="L135">        modelAndView.addObject(&quot;languages&quot;, languages);</span>
    +<span class="nc" id="L136">        modelAndView.addObject(&quot;currentPage&quot;, &quot;ocr-pdf&quot;);</span>
    +<span class="nc" id="L137">        return modelAndView;</span>
    +    }
    +
    +    @GetMapping(&quot;/add-image&quot;)
    +    @Hidden
    +    public String overlayImage(Model model) {
    +<span class="nc" id="L143">        model.addAttribute(&quot;currentPage&quot;, &quot;add-image&quot;);</span>
    +<span class="nc" id="L144">        return &quot;misc/add-image&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/adjust-contrast&quot;)
    +    @Hidden
    +    public String contrast(Model model) {
    +<span class="nc" id="L150">        model.addAttribute(&quot;currentPage&quot;, &quot;adjust-contrast&quot;);</span>
    +<span class="nc" id="L151">        return &quot;misc/adjust-contrast&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/repair&quot;)
    +    @Hidden
    +    public String repairForm(Model model) {
    +<span class="nc" id="L157">        model.addAttribute(&quot;currentPage&quot;, &quot;repair&quot;);</span>
    +<span class="nc" id="L158">        return &quot;misc/repair&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/remove-blanks&quot;)
    +    @Hidden
    +    public String removeBlanksForm(Model model) {
    +<span class="nc" id="L164">        model.addAttribute(&quot;currentPage&quot;, &quot;remove-blanks&quot;);</span>
    +<span class="nc" id="L165">        return &quot;misc/remove-blanks&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/remove-annotations&quot;)
    +    @Hidden
    +    public String removeAnnotationsForm(Model model) {
    +<span class="nc" id="L171">        model.addAttribute(&quot;currentPage&quot;, &quot;remove-annotations&quot;);</span>
    +<span class="nc" id="L172">        return &quot;misc/remove-annotations&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/auto-crop&quot;)
    +    @Hidden
    +    public String autoCropForm(Model model) {
    +<span class="nc" id="L178">        model.addAttribute(&quot;currentPage&quot;, &quot;auto-crop&quot;);</span>
    +<span class="nc" id="L179">        return &quot;misc/auto-crop&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/auto-rename&quot;)
    +    @Hidden
    +    public String autoRenameForm(Model model) {
    +<span class="nc" id="L185">        model.addAttribute(&quot;currentPage&quot;, &quot;auto-rename&quot;);</span>
    +<span class="nc" id="L186">        return &quot;misc/auto-rename&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/SecurityWebController.html b/testResult/html/stirling.software.SPDF.controller.web/SecurityWebController.html
    new file mode 100644
    index 000000000..598082551
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/SecurityWebController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityWebController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">SecurityWebController</span></div><h1>SecurityWebController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">80 of 80</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">23</td><td class="ctr2">23</td><td class="ctr1">12</td><td class="ctr2">12</td></tr></tfoot><tbody><tr><td id="a2"><a href="SecurityWebController.java.html#L17" class="el_method">autoRedactForm(Model)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a8"><a href="SecurityWebController.java.html#L23" class="el_method">redactForm(Model)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="SecurityWebController.java.html#L30" class="el_method">addPasswordForm(Model)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a7"><a href="SecurityWebController.java.html#L37" class="el_method">permissionsForm(Model)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a9"><a href="SecurityWebController.java.html#L44" class="el_method">removePasswordForm(Model)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="SecurityWebController.java.html#L51" class="el_method">addWatermarkForm(Model)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">2</td><td class="ctr2" id="i5">2</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a3"><a href="SecurityWebController.java.html#L58" class="el_method">certSignForm(Model)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">2</td><td class="ctr2" id="i6">2</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a4"><a href="SecurityWebController.java.html#L65" class="el_method">certSignVerifyForm(Model)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">2</td><td class="ctr2" id="i7">2</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a5"><a href="SecurityWebController.java.html#L72" class="el_method">certUnSignForm(Model)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a10"><a href="SecurityWebController.java.html#L79" class="el_method">sanitizeForm(Model)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a6"><a href="SecurityWebController.java.html#L86" class="el_method">getInfo(Model)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">2</td><td class="ctr2" id="i10">2</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a11"><a href="SecurityWebController.java.html#L12" class="el_method">SecurityWebController()</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="3" alt="3"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">1</td><td class="ctr2" id="i11">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/SecurityWebController.java.html b/testResult/html/stirling.software.SPDF.controller.web/SecurityWebController.java.html
    new file mode 100644
    index 000000000..303ec694f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/SecurityWebController.java.html
    @@ -0,0 +1,90 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SecurityWebController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">SecurityWebController.java</span></div><h1>SecurityWebController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import org.springframework.stereotype.Controller;
    +import org.springframework.ui.Model;
    +import org.springframework.web.bind.annotation.GetMapping;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +@Controller
    +@Tag(name = &quot;Security&quot;, description = &quot;Security APIs&quot;)
    +<span class="nc" id="L12">public class SecurityWebController {</span>
    +
    +    @GetMapping(&quot;/auto-redact&quot;)
    +    @Hidden
    +    public String autoRedactForm(Model model) {
    +<span class="nc" id="L17">        model.addAttribute(&quot;currentPage&quot;, &quot;auto-redact&quot;);</span>
    +<span class="nc" id="L18">        return &quot;security/auto-redact&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/redact&quot;)
    +    public String redactForm(Model model) {
    +<span class="nc" id="L23">        model.addAttribute(&quot;currentPage&quot;, &quot;redact&quot;);</span>
    +<span class="nc" id="L24">        return &quot;security/redact&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/add-password&quot;)
    +    @Hidden
    +    public String addPasswordForm(Model model) {
    +<span class="nc" id="L30">        model.addAttribute(&quot;currentPage&quot;, &quot;add-password&quot;);</span>
    +<span class="nc" id="L31">        return &quot;security/add-password&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/change-permissions&quot;)
    +    @Hidden
    +    public String permissionsForm(Model model) {
    +<span class="nc" id="L37">        model.addAttribute(&quot;currentPage&quot;, &quot;change-permissions&quot;);</span>
    +<span class="nc" id="L38">        return &quot;security/change-permissions&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/remove-password&quot;)
    +    @Hidden
    +    public String removePasswordForm(Model model) {
    +<span class="nc" id="L44">        model.addAttribute(&quot;currentPage&quot;, &quot;remove-password&quot;);</span>
    +<span class="nc" id="L45">        return &quot;security/remove-password&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/add-watermark&quot;)
    +    @Hidden
    +    public String addWatermarkForm(Model model) {
    +<span class="nc" id="L51">        model.addAttribute(&quot;currentPage&quot;, &quot;add-watermark&quot;);</span>
    +<span class="nc" id="L52">        return &quot;security/add-watermark&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/cert-sign&quot;)
    +    @Hidden
    +    public String certSignForm(Model model) {
    +<span class="nc" id="L58">        model.addAttribute(&quot;currentPage&quot;, &quot;cert-sign&quot;);</span>
    +<span class="nc" id="L59">        return &quot;security/cert-sign&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/validate-signature&quot;)
    +    @Hidden
    +    public String certSignVerifyForm(Model model) {
    +<span class="nc" id="L65">        model.addAttribute(&quot;currentPage&quot;, &quot;validate-signature&quot;);</span>
    +<span class="nc" id="L66">        return &quot;security/validate-signature&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/remove-cert-sign&quot;)
    +    @Hidden
    +    public String certUnSignForm(Model model) {
    +<span class="nc" id="L72">        model.addAttribute(&quot;currentPage&quot;, &quot;remove-cert-sign&quot;);</span>
    +<span class="nc" id="L73">        return &quot;security/remove-cert-sign&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/sanitize-pdf&quot;)
    +    @Hidden
    +    public String sanitizeForm(Model model) {
    +<span class="nc" id="L79">        model.addAttribute(&quot;currentPage&quot;, &quot;sanitize-pdf&quot;);</span>
    +<span class="nc" id="L80">        return &quot;security/sanitize-pdf&quot;;</span>
    +    }
    +
    +    @GetMapping(&quot;/get-info-on-pdf&quot;)
    +    @Hidden
    +    public String getInfo(Model model) {
    +<span class="nc" id="L86">        model.addAttribute(&quot;currentPage&quot;, &quot;get-info-on-pdf&quot;);</span>
    +<span class="nc" id="L87">        return &quot;security/get-info-on-pdf&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/SignatureController.html b/testResult/html/stirling.software.SPDF.controller.web/SignatureController.html
    new file mode 100644
    index 000000000..4c13e6f44
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/SignatureController.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SignatureController</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_class">SignatureController</span></div><h1>SignatureController</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">40 of 40</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="SignatureController.java.html#L35" class="el_method">getSignature(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="31" alt="31"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">9</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="SignatureController.java.html#L27" class="el_method">SignatureController(SignatureService, UserServiceInterface)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/SignatureController.java.html b/testResult/html/stirling.software.SPDF.controller.web/SignatureController.java.html
    new file mode 100644
    index 000000000..ad120ee06
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/SignatureController.java.html
    @@ -0,0 +1,50 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SignatureController.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.controller.web</a> &gt; <span class="el_source">SignatureController.java</span></div><h1>SignatureController.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.controller.web;
    +
    +import java.io.IOException;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.stereotype.Controller;
    +import org.springframework.web.bind.annotation.GetMapping;
    +import org.springframework.web.bind.annotation.PathVariable;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +
    +import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
    +import stirling.software.SPDF.service.SignatureService;
    +
    +@Controller
    +@RequestMapping(&quot;/api/v1/general&quot;)
    +public class SignatureController {
    +
    +    private final SignatureService signatureService;
    +
    +    private final UserServiceInterface userService;
    +
    +    public SignatureController(
    +            SignatureService signatureService,
    +<span class="nc" id="L27">            @Autowired(required = false) UserServiceInterface userService) {</span>
    +<span class="nc" id="L28">        this.signatureService = signatureService;</span>
    +<span class="nc" id="L29">        this.userService = userService;</span>
    +<span class="nc" id="L30">    }</span>
    +
    +    @GetMapping(&quot;/sign/{fileName}&quot;)
    +    public ResponseEntity&lt;byte[]&gt; getSignature(@PathVariable(name = &quot;fileName&quot;) String fileName)
    +            throws IOException {
    +<span class="nc" id="L35">        String username = &quot;NON_SECURITY_USER&quot;;</span>
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">        if (userService != null) {</span>
    +<span class="nc" id="L37">            username = userService.getCurrentUsername();</span>
    +        }
    +        // Verify access permission
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">        if (!signatureService.hasAccessToFile(username, fileName)) {</span>
    +<span class="nc" id="L41">            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();</span>
    +        }
    +<span class="nc" id="L43">        byte[] imageBytes = signatureService.getSignatureBytes(username, fileName);</span>
    +<span class="nc" id="L44">        return ResponseEntity.ok()</span>
    +<span class="nc" id="L45">                .contentType( // Adjust based on file type</span>
    +                        MediaType.IMAGE_JPEG)
    +<span class="nc" id="L47">                .body(imageBytes);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/index.html b/testResult/html/stirling.software.SPDF.controller.web/index.html
    new file mode 100644
    index 000000000..485b9a2aa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.web</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.web</span></div><h1>stirling.software.SPDF.controller.web</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2,657 of 2,657</td><td class="ctr2">0%</td><td class="bar">269 of 269</td><td class="ctr2">0%</td><td class="ctr1">293</td><td class="ctr2">293</td><td class="ctr1">717</td><td class="ctr2">717</td><td class="ctr1">142</td><td class="ctr2">142</td><td class="ctr1">13</td><td class="ctr2">13</td></tr></tfoot><tbody><tr><td id="a0"><a href="AccountWebController.html" class="el_class">AccountWebController</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="874" alt="874"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="145" alt="145"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">96</td><td class="ctr2" id="g0">96</td><td class="ctr1" id="h0">240</td><td class="ctr2" id="i0">240</td><td class="ctr1" id="j5">9</td><td class="ctr2" id="k5">9</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a8"><a href="MetricsController.html" class="el_class">MetricsController</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="86" height="10" title="629" alt="629"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="69" height="10" title="84" alt="84"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">68</td><td class="ctr2" id="g1">68</td><td class="ctr1" id="h1">155</td><td class="ctr2" id="i1">155</td><td class="ctr1" id="j1">26</td><td class="ctr2" id="k1">26</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a3"><a href="GeneralWebController.html" class="el_class">GeneralWebController</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="451" alt="451"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="24" alt="24"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">42</td><td class="ctr2" id="g2">42</td><td class="ctr1" id="h2">122</td><td class="ctr2" id="i2">122</td><td class="ctr1" id="j0">28</td><td class="ctr2" id="k0">28</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a10"><a href="OtherWebController.html" class="el_class">OtherWebController</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="217" alt="217"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">27</td><td class="ctr2" id="g3">27</td><td class="ctr1" id="h3">59</td><td class="ctr2" id="i3">59</td><td class="ctr1" id="j2">25</td><td class="ctr2" id="k2">25</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a1"><a href="ConverterWebController.html" class="el_class">ConverterWebController</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="139" alt="139"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f4">15</td><td class="ctr2" id="g4">15</td><td class="ctr1" id="h4">37</td><td class="ctr2" id="i4">37</td><td class="ctr1" id="j3">15</td><td class="ctr2" id="k3">15</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a6"><a href="HomeWebController.html" class="el_class">HomeWebController</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="96" alt="96"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f6">10</td><td class="ctr2" id="g6">10</td><td class="ctr1" id="h5">28</td><td class="ctr2" id="i5">28</td><td class="ctr1" id="j6">9</td><td class="ctr2" id="k6">9</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a11"><a href="SecurityWebController.html" class="el_class">SecurityWebController</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="80" alt="80"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f5">12</td><td class="ctr2" id="g5">12</td><td class="ctr1" id="h6">23</td><td class="ctr2" id="i6">23</td><td class="ctr1" id="j4">12</td><td class="ctr2" id="k4">12</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a2"><a href="DatabaseWebController.html" class="el_class">DatabaseWebController</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="58" alt="58"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f8">5</td><td class="ctr2" id="g8">5</td><td class="ctr1" id="h7">16</td><td class="ctr2" id="i7">16</td><td class="ctr1" id="j9">2</td><td class="ctr2" id="k9">2</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a12"><a href="SignatureController.html" class="el_class">SignatureController</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="40" alt="40"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f10">4</td><td class="ctr2" id="g10">4</td><td class="ctr1" id="h9">13</td><td class="ctr2" id="i9">13</td><td class="ctr1" id="j10">2</td><td class="ctr2" id="k10">2</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a4"><a href="GeneralWebController$FontResource.html" class="el_class">GeneralWebController.FontResource</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="38" alt="38"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f7">7</td><td class="ctr2" id="g7">7</td><td class="ctr1" id="h8">14</td><td class="ctr2" id="i8">14</td><td class="ctr1" id="j7">7</td><td class="ctr2" id="k7">7</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a9"><a href="MetricsController$EndpointCount.html" class="el_class">MetricsController.EndpointCount</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="23" alt="23"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f9">5</td><td class="ctr2" id="g9">5</td><td class="ctr1" id="h10">10</td><td class="ctr2" id="i10">10</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a7"><a href="HomeWebController$1.html" class="el_class">HomeWebController.new TypeReference() {...}</a></td><td class="bar" id="b11"/><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">1</td><td class="ctr2" id="i11">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a5"><a href="GeneralWebController$1.html" class="el_class">GeneralWebController.new TypeReference() {...}</a></td><td class="bar" id="b12"/><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h12">1</td><td class="ctr2" id="i12">1</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m12">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.controller.web/index.source.html b/testResult/html/stirling.software.SPDF.controller.web/index.source.html
    new file mode 100644
    index 000000000..83065a852
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.controller.web/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.controller.web</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.controller.web</span></div><h1>stirling.software.SPDF.controller.web</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2,657 of 2,657</td><td class="ctr2">0%</td><td class="bar">269 of 269</td><td class="ctr2">0%</td><td class="ctr1">293</td><td class="ctr2">293</td><td class="ctr1">717</td><td class="ctr2">717</td><td class="ctr1">142</td><td class="ctr2">142</td><td class="ctr1">13</td><td class="ctr2">13</td></tr></tfoot><tbody><tr><td id="a0"><a href="AccountWebController.java.html" class="el_source">AccountWebController.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="874" alt="874"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="145" alt="145"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">96</td><td class="ctr2" id="g0">96</td><td class="ctr1" id="h0">240</td><td class="ctr2" id="i0">240</td><td class="ctr1" id="j6">9</td><td class="ctr2" id="k6">9</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a5"><a href="MetricsController.java.html" class="el_source">MetricsController.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="89" height="10" title="652" alt="652"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="69" height="10" title="84" alt="84"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">73</td><td class="ctr2" id="g1">73</td><td class="ctr1" id="h1">165</td><td class="ctr2" id="i1">165</td><td class="ctr1" id="j1">31</td><td class="ctr2" id="k1">31</td><td class="ctr1" id="l1">2</td><td class="ctr2" id="m1">2</td></tr><tr><td id="a3"><a href="GeneralWebController.java.html" class="el_source">GeneralWebController.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="67" height="10" title="495" alt="495"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="24" alt="24"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">50</td><td class="ctr2" id="g2">50</td><td class="ctr1" id="h2">136</td><td class="ctr2" id="i2">136</td><td class="ctr1" id="j0">36</td><td class="ctr2" id="k0">36</td><td class="ctr1" id="l0">3</td><td class="ctr2" id="m0">3</td></tr><tr><td id="a6"><a href="OtherWebController.java.html" class="el_source">OtherWebController.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="217" alt="217"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">27</td><td class="ctr2" id="g3">27</td><td class="ctr1" id="h3">59</td><td class="ctr2" id="i3">59</td><td class="ctr1" id="j2">25</td><td class="ctr2" id="k2">25</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a1"><a href="ConverterWebController.java.html" class="el_source">ConverterWebController.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="139" alt="139"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f4">15</td><td class="ctr2" id="g4">15</td><td class="ctr1" id="h4">37</td><td class="ctr2" id="i4">37</td><td class="ctr1" id="j3">15</td><td class="ctr2" id="k3">15</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a4"><a href="HomeWebController.java.html" class="el_source">HomeWebController.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="102" alt="102"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f6">11</td><td class="ctr2" id="g6">11</td><td class="ctr1" id="h5">28</td><td class="ctr2" id="i5">28</td><td class="ctr1" id="j5">10</td><td class="ctr2" id="k5">10</td><td class="ctr1" id="l2">2</td><td class="ctr2" id="m2">2</td></tr><tr><td id="a7"><a href="SecurityWebController.java.html" class="el_source">SecurityWebController.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="80" alt="80"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f5">12</td><td class="ctr2" id="g5">12</td><td class="ctr1" id="h6">23</td><td class="ctr2" id="i6">23</td><td class="ctr1" id="j4">12</td><td class="ctr2" id="k4">12</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a2"><a href="DatabaseWebController.java.html" class="el_source">DatabaseWebController.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="58" alt="58"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="6" alt="6"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f7">5</td><td class="ctr2" id="g7">5</td><td class="ctr1" id="h7">16</td><td class="ctr2" id="i7">16</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a8"><a href="SignatureController.java.html" class="el_source">SignatureController.java</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="40" alt="40"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f8">4</td><td class="ctr2" id="g8">4</td><td class="ctr1" id="h8">13</td><td class="ctr2" id="i8">13</td><td class="ctr1" id="j8">2</td><td class="ctr2" id="k8">2</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.html b/testResult/html/stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.html
    new file mode 100644
    index 000000000..a191f9f0e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertPDFToMarkdown</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.api.converters</a> &gt; <span class="el_class">ConvertPDFToMarkdown</span></div><h1>ConvertPDFToMarkdown</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 14</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="ConvertPDFToMarkdown.java.html#L28" class="el_method">processPdfToMarkdown(PDFFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="11" alt="11"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ConvertPDFToMarkdown.java.html#L19" class="el_method">ConvertPDFToMarkdown()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="32" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.java.html b/testResult/html/stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.java.html
    new file mode 100644
    index 000000000..b337320ca
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.converters/ConvertPDFToMarkdown.java.html
    @@ -0,0 +1,33 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ConvertPDFToMarkdown.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.api.converters</a> &gt; <span class="el_source">ConvertPDFToMarkdown.java</span></div><h1>ConvertPDFToMarkdown.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.api.converters;
    +
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.bind.annotation.ModelAttribute;
    +import org.springframework.web.bind.annotation.PostMapping;
    +import org.springframework.web.bind.annotation.RequestMapping;
    +import org.springframework.web.bind.annotation.RestController;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.swagger.v3.oas.annotations.Operation;
    +import io.swagger.v3.oas.annotations.tags.Tag;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.utils.PDFToFile;
    +
    +@RestController
    +@Tag(name = &quot;Convert&quot;, description = &quot;Convert APIs&quot;)
    +@RequestMapping(&quot;/api/v1/convert&quot;)
    +<span class="fc" id="L19">public class ConvertPDFToMarkdown {</span>
    +
    +    @PostMapping(consumes = &quot;multipart/form-data&quot;, value = &quot;/pdf/markdown&quot;)
    +    @Operation(
    +            summary = &quot;Convert PDF to Markdown&quot;,
    +            description =
    +                    &quot;This endpoint converts a PDF file to Markdown format. Input:PDF Output:Markdown Type:SISO&quot;)
    +    public ResponseEntity&lt;byte[]&gt; processPdfToMarkdown(@ModelAttribute PDFFile request)
    +            throws Exception {
    +<span class="fc" id="L28">        MultipartFile inputFile = request.getFileInput();</span>
    +<span class="fc" id="L29">        PDFToFile pdfToFile = new PDFToFile();</span>
    +<span class="fc" id="L30">        return pdfToFile.processPdfToMarkdown(inputFile);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.converters/index.html b/testResult/html/stirling.software.SPDF.model.api.converters/index.html
    new file mode 100644
    index 000000000..434ec160c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.converters/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.api.converters</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.api.converters</span></div><h1>stirling.software.SPDF.model.api.converters</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 14</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ConvertPDFToMarkdown.html" class="el_class">ConvertPDFToMarkdown</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">0</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.converters/index.source.html b/testResult/html/stirling.software.SPDF.model.api.converters/index.source.html
    new file mode 100644
    index 000000000..6b2bee0e6
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.converters/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.api.converters</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.api.converters</span></div><h1>stirling.software.SPDF.model.api.converters</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 14</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">4</td><td class="ctr1">0</td><td class="ctr2">2</td><td class="ctr1">0</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ConvertPDFToMarkdown.java.html" class="el_source">ConvertPDFToMarkdown.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">0</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.misc/HighContrastColorCombination.html b/testResult/html/stirling.software.SPDF.model.api.misc/HighContrastColorCombination.html
    new file mode 100644
    index 000000000..952bfb3cf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.misc/HighContrastColorCombination.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>HighContrastColorCombination</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.api.misc</a> &gt; <span class="el_class">HighContrastColorCombination</span></div><h1>HighContrastColorCombination</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">27 of 27</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="HighContrastColorCombination.java.html#L3" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="27" alt="27"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.misc/HighContrastColorCombination.java.html b/testResult/html/stirling.software.SPDF.model.api.misc/HighContrastColorCombination.java.html
    new file mode 100644
    index 000000000..908830d48
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.misc/HighContrastColorCombination.java.html
    @@ -0,0 +1,9 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>HighContrastColorCombination.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.api.misc</a> &gt; <span class="el_source">HighContrastColorCombination.java</span></div><h1>HighContrastColorCombination.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.api.misc;
    +
    +<span class="nc" id="L3">public enum HighContrastColorCombination {</span>
    +<span class="nc" id="L4">    WHITE_TEXT_ON_BLACK,</span>
    +<span class="nc" id="L5">    BLACK_TEXT_ON_WHITE,</span>
    +<span class="nc" id="L6">    YELLOW_TEXT_ON_BLACK,</span>
    +<span class="nc" id="L7">    GREEN_TEXT_ON_BLACK,</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.misc/OptimizePdfRequest.html b/testResult/html/stirling.software.SPDF.model.api.misc/OptimizePdfRequest.html
    new file mode 100644
    index 000000000..34141bf5a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.misc/OptimizePdfRequest.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OptimizePdfRequest</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.api.misc</a> &gt; <span class="el_class">OptimizePdfRequest</span></div><h1>OptimizePdfRequest</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">3 of 3</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="OptimizePdfRequest.java.html#L40" class="el_method">getGrayscale()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="3" alt="3"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.misc/OptimizePdfRequest.java.html b/testResult/html/stirling.software.SPDF.model.api.misc/OptimizePdfRequest.java.html
    new file mode 100644
    index 000000000..01b892faf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.misc/OptimizePdfRequest.java.html
    @@ -0,0 +1,43 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>OptimizePdfRequest.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.api.misc</a> &gt; <span class="el_source">OptimizePdfRequest.java</span></div><h1>OptimizePdfRequest.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.api.misc;
    +
    +import io.swagger.v3.oas.annotations.media.Schema;
    +
    +import lombok.Data;
    +import lombok.EqualsAndHashCode;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +
    +@Data
    +@EqualsAndHashCode(callSuper = true)
    +public class OptimizePdfRequest extends PDFFile {
    +
    +    @Schema(
    +            description =
    +                    &quot;The level of optimization to apply to the PDF file. Higher values indicate greater compression but may reduce quality.&quot;,
    +            allowableValues = {&quot;1&quot;, &quot;2&quot;, &quot;3&quot;, &quot;4&quot;, &quot;5&quot;, &quot;6&quot;, &quot;7&quot;, &quot;8&quot;, &quot;9&quot;})
    +    private Integer optimizeLevel;
    +
    +    @Schema(description = &quot;The expected output size, e.g. '100MB', '25KB', etc.&quot;)
    +    private String expectedOutputSize;
    +
    +    @Schema(
    +            description = &quot;Whether to linearize the PDF for faster web viewing. Default is false.&quot;,
    +            defaultValue = &quot;false&quot;)
    +    private Boolean linearize = false;
    +
    +    @Schema(
    +            description =
    +                    &quot;Whether to normalize the PDF content for better compatibility. Default is false.&quot;,
    +            defaultValue = &quot;false&quot;)
    +    private Boolean normalize = false;
    +
    +    @Schema(
    +            description = &quot;Whether to convert the PDF to grayscale. Default is false.&quot;,
    +            defaultValue = &quot;false&quot;)
    +    private Boolean grayscale = false;
    +
    +    public Boolean getGrayscale() {
    +<span class="nc" id="L40">        return grayscale;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.misc/ReplaceAndInvert.html b/testResult/html/stirling.software.SPDF.model.api.misc/ReplaceAndInvert.html
    new file mode 100644
    index 000000000..b4cbe382e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.misc/ReplaceAndInvert.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvert</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.api.misc</a> &gt; <span class="el_class">ReplaceAndInvert</span></div><h1>ReplaceAndInvert</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">21 of 21</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvert.java.html#L3" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="21" alt="21"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.misc/ReplaceAndInvert.java.html b/testResult/html/stirling.software.SPDF.model.api.misc/ReplaceAndInvert.java.html
    new file mode 100644
    index 000000000..1d7ce6586
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.misc/ReplaceAndInvert.java.html
    @@ -0,0 +1,8 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvert.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.api.misc</a> &gt; <span class="el_source">ReplaceAndInvert.java</span></div><h1>ReplaceAndInvert.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.api.misc;
    +
    +<span class="nc" id="L3">public enum ReplaceAndInvert {</span>
    +<span class="nc" id="L4">    HIGH_CONTRAST_COLOR,</span>
    +<span class="nc" id="L5">    CUSTOM_COLOR,</span>
    +<span class="nc" id="L6">    FULL_INVERSION,</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.misc/index.html b/testResult/html/stirling.software.SPDF.model.api.misc/index.html
    new file mode 100644
    index 000000000..f45346a0b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.misc/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.api.misc</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.api.misc</span></div><h1>stirling.software.SPDF.model.api.misc</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">51 of 51</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="HighContrastColorCombination.html" class="el_class">HighContrastColorCombination</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="27" alt="27"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a2"><a href="ReplaceAndInvert.html" class="el_class">ReplaceAndInvert</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="93" height="10" title="21" alt="21"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a1"><a href="OptimizePdfRequest.html" class="el_class">OptimizePdfRequest</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api.misc/index.source.html b/testResult/html/stirling.software.SPDF.model.api.misc/index.source.html
    new file mode 100644
    index 000000000..2ad40720c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api.misc/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.api.misc</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.api.misc</span></div><h1>stirling.software.SPDF.model.api.misc</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">51 of 51</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="HighContrastColorCombination.java.html" class="el_source">HighContrastColorCombination.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="27" alt="27"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a2"><a href="ReplaceAndInvert.java.html" class="el_source">ReplaceAndInvert.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="93" height="10" title="21" alt="21"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a1"><a href="OptimizePdfRequest.java.html" class="el_source">OptimizePdfRequest.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api/PDFWithPageNums.html b/testResult/html/stirling.software.SPDF.model.api/PDFWithPageNums.html
    new file mode 100644
    index 000000000..2d88fee8c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api/PDFWithPageNums.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PDFWithPageNums</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.api</a> &gt; <span class="el_class">PDFWithPageNums</span></div><h1>PDFWithPageNums</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">15 of 15</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="PDFWithPageNums.java.html#L35" class="el_method">getPageNumbersList(PDDocument, boolean)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="11" alt="11"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="PDFWithPageNums.java.html#L20" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api/PDFWithPageNums.java.html b/testResult/html/stirling.software.SPDF.model.api/PDFWithPageNums.java.html
    new file mode 100644
    index 000000000..7cc05ec73
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api/PDFWithPageNums.java.html
    @@ -0,0 +1,40 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PDFWithPageNums.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.api</a> &gt; <span class="el_source">PDFWithPageNums.java</span></div><h1>PDFWithPageNums.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.api;
    +
    +import java.util.List;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +
    +import io.swagger.v3.oas.annotations.Hidden;
    +import io.swagger.v3.oas.annotations.media.Schema;
    +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
    +
    +import lombok.Data;
    +import lombok.EqualsAndHashCode;
    +import lombok.NoArgsConstructor;
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.utils.GeneralUtils;
    +
    +@Data
    +@NoArgsConstructor
    +<span class="nc" id="L20">@Slf4j</span>
    +@EqualsAndHashCode(callSuper = true)
    +public class PDFWithPageNums extends PDFFile {
    +
    +    @Schema(
    +            description =
    +                    &quot;The pages to select, Supports ranges (e.g., '1,3,5-9'), or 'all' or functions in the&quot;
    +                            + &quot; format 'an+b' where 'a' is the multiplier of the page number 'n', and 'b' is a&quot;
    +                            + &quot; constant (e.g., '2n+1', '3n', '6n-5')\&quot;&quot;,
    +            defaultValue = &quot;all&quot;,
    +            requiredMode = RequiredMode.NOT_REQUIRED)
    +    private String pageNumbers;
    +
    +    @Hidden
    +    public List&lt;Integer&gt; getPageNumbersList(PDDocument doc, boolean oneBased) {
    +<span class="nc" id="L35">        int pageCount = 0;</span>
    +<span class="nc" id="L36">        pageCount = doc.getNumberOfPages();</span>
    +<span class="nc" id="L37">        return GeneralUtils.parsePageList(pageNumbers, pageCount, oneBased);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api/index.html b/testResult/html/stirling.software.SPDF.model.api/index.html
    new file mode 100644
    index 000000000..d7135f2fa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.api</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.api</span></div><h1>stirling.software.SPDF.model.api</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">15 of 15</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="PDFWithPageNums.html" class="el_class">PDFWithPageNums</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="15" alt="15"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">2</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.api/index.source.html b/testResult/html/stirling.software.SPDF.model.api/index.source.html
    new file mode 100644
    index 000000000..88d7c352e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.api/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.api</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.api</span></div><h1>stirling.software.SPDF.model.api</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">15 of 15</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="PDFWithPageNums.java.html" class="el_source">PDFWithPageNums.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="15" alt="15"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">2</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/BackupNotFoundException.html b/testResult/html/stirling.software.SPDF.model.exception/BackupNotFoundException.html
    new file mode 100644
    index 000000000..f57ae9b49
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/BackupNotFoundException.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>BackupNotFoundException</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.exception</a> &gt; <span class="el_class">BackupNotFoundException</span></div><h1>BackupNotFoundException</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="BackupNotFoundException.java.html#L5" class="el_method">BackupNotFoundException(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/BackupNotFoundException.java.html b/testResult/html/stirling.software.SPDF.model.exception/BackupNotFoundException.java.html
    new file mode 100644
    index 000000000..3bae21756
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/BackupNotFoundException.java.html
    @@ -0,0 +1,8 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>BackupNotFoundException.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.exception</a> &gt; <span class="el_source">BackupNotFoundException.java</span></div><h1>BackupNotFoundException.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.exception;
    +
    +public class BackupNotFoundException extends RuntimeException {
    +    public BackupNotFoundException(String message) {
    +<span class="nc" id="L5">        super(message);</span>
    +<span class="nc" id="L6">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/NoProviderFoundException.html b/testResult/html/stirling.software.SPDF.model.exception/NoProviderFoundException.html
    new file mode 100644
    index 000000000..2c49b80cd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/NoProviderFoundException.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>NoProviderFoundException</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.exception</a> &gt; <span class="el_class">NoProviderFoundException</span></div><h1>NoProviderFoundException</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">9 of 9</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="NoProviderFoundException.java.html#L9" class="el_method">NoProviderFoundException(String, Throwable)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="5" alt="5"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="NoProviderFoundException.java.html#L5" class="el_method">NoProviderFoundException(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/NoProviderFoundException.java.html b/testResult/html/stirling.software.SPDF.model.exception/NoProviderFoundException.java.html
    new file mode 100644
    index 000000000..c1ccbb762
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/NoProviderFoundException.java.html
    @@ -0,0 +1,12 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>NoProviderFoundException.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.exception</a> &gt; <span class="el_source">NoProviderFoundException.java</span></div><h1>NoProviderFoundException.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.exception;
    +
    +public class NoProviderFoundException extends Exception {
    +    public NoProviderFoundException(String message) {
    +<span class="nc" id="L5">        super(message);</span>
    +<span class="nc" id="L6">    }</span>
    +
    +    public NoProviderFoundException(String message, Throwable cause) {
    +<span class="nc" id="L9">        super(message, cause);</span>
    +<span class="nc" id="L10">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/UnsupportedProviderException.html b/testResult/html/stirling.software.SPDF.model.exception/UnsupportedProviderException.html
    new file mode 100644
    index 000000000..bf022799e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/UnsupportedProviderException.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UnsupportedProviderException</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.exception</a> &gt; <span class="el_class">UnsupportedProviderException</span></div><h1>UnsupportedProviderException</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="UnsupportedProviderException.java.html#L5" class="el_method">UnsupportedProviderException(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/UnsupportedProviderException.java.html b/testResult/html/stirling.software.SPDF.model.exception/UnsupportedProviderException.java.html
    new file mode 100644
    index 000000000..d984febff
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/UnsupportedProviderException.java.html
    @@ -0,0 +1,8 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UnsupportedProviderException.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.exception</a> &gt; <span class="el_source">UnsupportedProviderException.java</span></div><h1>UnsupportedProviderException.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.exception;
    +
    +public class UnsupportedProviderException extends Exception {
    +    public UnsupportedProviderException(String message) {
    +<span class="nc" id="L5">        super(message);</span>
    +<span class="nc" id="L6">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/UnsupportedUsernameAttribute.html b/testResult/html/stirling.software.SPDF.model.exception/UnsupportedUsernameAttribute.html
    new file mode 100644
    index 000000000..049593016
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/UnsupportedUsernameAttribute.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UnsupportedUsernameAttribute</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.exception</a> &gt; <span class="el_class">UnsupportedUsernameAttribute</span></div><h1>UnsupportedUsernameAttribute</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="UnsupportedUsernameAttribute.java.html#L5" class="el_method">UnsupportedUsernameAttribute(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/UnsupportedUsernameAttribute.java.html b/testResult/html/stirling.software.SPDF.model.exception/UnsupportedUsernameAttribute.java.html
    new file mode 100644
    index 000000000..7d4f31ae0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/UnsupportedUsernameAttribute.java.html
    @@ -0,0 +1,8 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UnsupportedUsernameAttribute.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.exception</a> &gt; <span class="el_source">UnsupportedUsernameAttribute.java</span></div><h1>UnsupportedUsernameAttribute.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.exception;
    +
    +public class UnsupportedUsernameAttribute extends RuntimeException {
    +    public UnsupportedUsernameAttribute(String message) {
    +<span class="nc" id="L5">        super(message);</span>
    +<span class="nc" id="L6">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/index.html b/testResult/html/stirling.software.SPDF.model.exception/index.html
    new file mode 100644
    index 000000000..16c7fc3c4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.exception</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.exception</span></div><h1>stirling.software.SPDF.model.exception</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">21 of 21</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="NoProviderFoundException.html" class="el_class">NoProviderFoundException</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">2</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="BackupNotFoundException.html" class="el_class">BackupNotFoundException</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a2"><a href="UnsupportedProviderException.html" class="el_class">UnsupportedProviderException</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="UnsupportedUsernameAttribute.html" class="el_class">UnsupportedUsernameAttribute</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.exception/index.source.html b/testResult/html/stirling.software.SPDF.model.exception/index.source.html
    new file mode 100644
    index 000000000..0b4705b1b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.exception/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.exception</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.exception</span></div><h1>stirling.software.SPDF.model.exception</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">21 of 21</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="NoProviderFoundException.java.html" class="el_source">NoProviderFoundException.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">2</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a2"><a href="UnsupportedProviderException.java.html" class="el_source">UnsupportedProviderException.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a0"><a href="BackupNotFoundException.java.html" class="el_source">BackupNotFoundException.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="UnsupportedUsernameAttribute.java.html" class="el_source">UnsupportedUsernameAttribute.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/GitHubProvider.html b/testResult/html/stirling.software.SPDF.model.provider/GitHubProvider.html
    new file mode 100644
    index 000000000..f60e4ea4c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/GitHubProvider.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GitHubProvider</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.provider</a> &gt; <span class="el_class">GitHubProvider</span></div><h1>GitHubProvider</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">65 of 65</td><td class="ctr2">0%</td><td class="bar">10 of 10</td><td class="ctr2">0%</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a7"><a href="GitHubProvider.java.html#L76" class="el_method">toString()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="GitHubProvider.java.html#L64" class="el_method">getScopes()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="108" height="10" title="18" alt="18"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a6"><a href="GitHubProvider.java.html#L24" class="el_method">GitHubProvider(String, String, Collection, UsernameAttribute)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="102" height="10" title="17" alt="17"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="GitHubProvider.java.html#L39" class="el_method">getAuthorizationUri()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="GitHubProvider.java.html#L44" class="el_method">getTokenUri()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="GitHubProvider.java.html#L49" class="el_method">getUserInfoUri()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="GitHubProvider.java.html#L54" class="el_method">getName()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a1"><a href="GitHubProvider.java.html#L59" class="el_method">getClientName()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/GitHubProvider.java.html b/testResult/html/stirling.software.SPDF.model.provider/GitHubProvider.java.html
    new file mode 100644
    index 000000000..9425b729d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/GitHubProvider.java.html
    @@ -0,0 +1,87 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GitHubProvider.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.provider</a> &gt; <span class="el_source">GitHubProvider.java</span></div><h1>GitHubProvider.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.provider;
    +
    +import java.util.ArrayList;
    +import java.util.Collection;
    +
    +import lombok.NoArgsConstructor;
    +
    +import stirling.software.SPDF.model.UsernameAttribute;
    +
    +@NoArgsConstructor
    +public class GitHubProvider extends Provider {
    +
    +    private static final String NAME = &quot;github&quot;;
    +    private static final String CLIENT_NAME = &quot;GitHub&quot;;
    +    private static final String AUTHORIZATION_URI = &quot;https://github.com/login/oauth/authorize&quot;;
    +    private static final String TOKEN_URI = &quot;https://github.com/login/oauth/access_token&quot;;
    +    private static final String USER_INFO_URI = &quot;https://api.github.com/user&quot;;
    +
    +    public GitHubProvider(
    +            String clientId,
    +            String clientSecret,
    +            Collection&lt;String&gt; scopes,
    +            UsernameAttribute useAsUsername) {
    +<span class="nc" id="L24">        super(</span>
    +                null,
    +                NAME,
    +                CLIENT_NAME,
    +                clientId,
    +                clientSecret,
    +                scopes,
    +<span class="nc bnc" id="L31" title="All 2 branches missed.">                useAsUsername != null ? useAsUsername : UsernameAttribute.LOGIN,</span>
    +                AUTHORIZATION_URI,
    +                TOKEN_URI,
    +                USER_INFO_URI);
    +<span class="nc" id="L35">    }</span>
    +
    +    @Override
    +    public String getAuthorizationUri() {
    +<span class="nc" id="L39">        return AUTHORIZATION_URI;</span>
    +    }
    +
    +    @Override
    +    public String getTokenUri() {
    +<span class="nc" id="L44">        return TOKEN_URI;</span>
    +    }
    +
    +    @Override
    +    public String getUserInfoUri() {
    +<span class="nc" id="L49">        return USER_INFO_URI;</span>
    +    }
    +
    +    @Override
    +    public String getName() {
    +<span class="nc" id="L54">        return NAME;</span>
    +    }
    +
    +    @Override
    +    public String getClientName() {
    +<span class="nc" id="L59">        return CLIENT_NAME;</span>
    +    }
    +
    +    @Override
    +    public Collection&lt;String&gt; getScopes() {
    +<span class="nc" id="L64">        Collection&lt;String&gt; scopes = super.getScopes();</span>
    +
    +<span class="nc bnc" id="L66" title="All 4 branches missed.">        if (scopes == null || scopes.isEmpty()) {</span>
    +<span class="nc" id="L67">            scopes = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L68">            scopes.add(&quot;read:user&quot;);</span>
    +        }
    +
    +<span class="nc" id="L71">        return scopes;</span>
    +    }
    +
    +    @Override
    +    public String toString() {
    +<span class="nc" id="L76">        return &quot;GitHub [clientId=&quot;</span>
    +<span class="nc" id="L77">                + getClientId()</span>
    +                + &quot;, clientSecret=&quot;
    +<span class="nc bnc" id="L79" title="All 4 branches missed.">                + (getClientSecret() != null &amp;&amp; !getClientSecret().isEmpty() ? &quot;*****&quot; : &quot;NULL&quot;)</span>
    +                + &quot;, scopes=&quot;
    +<span class="nc" id="L81">                + getScopes()</span>
    +                + &quot;, useAsUsername=&quot;
    +<span class="nc" id="L83">                + getUseAsUsername()</span>
    +                + &quot;]&quot;;
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/GoogleProvider.html b/testResult/html/stirling.software.SPDF.model.provider/GoogleProvider.html
    new file mode 100644
    index 000000000..b14f1f5ea
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/GoogleProvider.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GoogleProvider</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.provider</a> &gt; <span class="el_class">GoogleProvider</span></div><h1>GoogleProvider</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">65 of 65</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a3"><a href="GoogleProvider.java.html#L62" class="el_method">getScopes()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a7"><a href="GoogleProvider.java.html#L75" class="el_method">toString()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="109" height="10" title="20" alt="20"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a6"><a href="GoogleProvider.java.html#L25" class="el_method">GoogleProvider(String, String, Collection, UsernameAttribute)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="13" alt="13"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="GoogleProvider.java.html#L39" class="el_method">getAuthorizationUri()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="GoogleProvider.java.html#L43" class="el_method">getTokenUri()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="GoogleProvider.java.html#L47" class="el_method">getUserinfoUri()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="GoogleProvider.java.html#L52" class="el_method">getName()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a1"><a href="GoogleProvider.java.html#L57" class="el_method">getClientName()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/GoogleProvider.java.html b/testResult/html/stirling.software.SPDF.model.provider/GoogleProvider.java.html
    new file mode 100644
    index 000000000..3e09ea897
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/GoogleProvider.java.html
    @@ -0,0 +1,86 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GoogleProvider.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.provider</a> &gt; <span class="el_source">GoogleProvider.java</span></div><h1>GoogleProvider.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.provider;
    +
    +import java.util.ArrayList;
    +import java.util.Collection;
    +
    +import lombok.NoArgsConstructor;
    +
    +import stirling.software.SPDF.model.UsernameAttribute;
    +
    +@NoArgsConstructor
    +public class GoogleProvider extends Provider {
    +
    +    private static final String NAME = &quot;google&quot;;
    +    private static final String CLIENT_NAME = &quot;Google&quot;;
    +    private static final String AUTHORIZATION_URI = &quot;https://accounts.google.com/o/oauth2/v2/auth&quot;;
    +    private static final String TOKEN_URI = &quot;https://www.googleapis.com/oauth2/v4/token&quot;;
    +    private static final String USER_INFO_URI =
    +            &quot;https://www.googleapis.com/oauth2/v3/userinfo?alt=json&quot;;
    +
    +    public GoogleProvider(
    +            String clientId,
    +            String clientSecret,
    +            Collection&lt;String&gt; scopes,
    +            UsernameAttribute useAsUsername) {
    +<span class="nc" id="L25">        super(</span>
    +                null,
    +                NAME,
    +                CLIENT_NAME,
    +                clientId,
    +                clientSecret,
    +                scopes,
    +                useAsUsername,
    +                AUTHORIZATION_URI,
    +                TOKEN_URI,
    +                USER_INFO_URI);
    +<span class="nc" id="L36">    }</span>
    +
    +    public String getAuthorizationUri() {
    +<span class="nc" id="L39">        return AUTHORIZATION_URI;</span>
    +    }
    +
    +    public String getTokenUri() {
    +<span class="nc" id="L43">        return TOKEN_URI;</span>
    +    }
    +
    +    public String getUserinfoUri() {
    +<span class="nc" id="L47">        return USER_INFO_URI;</span>
    +    }
    +
    +    @Override
    +    public String getName() {
    +<span class="nc" id="L52">        return NAME;</span>
    +    }
    +
    +    @Override
    +    public String getClientName() {
    +<span class="nc" id="L57">        return CLIENT_NAME;</span>
    +    }
    +
    +    @Override
    +    public Collection&lt;String&gt; getScopes() {
    +<span class="nc" id="L62">        Collection&lt;String&gt; scopes = super.getScopes();</span>
    +
    +<span class="nc bnc" id="L64" title="All 4 branches missed.">        if (scopes == null || scopes.isEmpty()) {</span>
    +<span class="nc" id="L65">            scopes = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L66">            scopes.add(&quot;https://www.googleapis.com/auth/userinfo.email&quot;);</span>
    +<span class="nc" id="L67">            scopes.add(&quot;https://www.googleapis.com/auth/userinfo.profile&quot;);</span>
    +        }
    +
    +<span class="nc" id="L70">        return scopes;</span>
    +    }
    +
    +    @Override
    +    public String toString() {
    +<span class="nc" id="L75">        return &quot;Google [clientId=&quot;</span>
    +<span class="nc" id="L76">                + getClientId()</span>
    +                + &quot;, clientSecret=&quot;
    +<span class="nc bnc" id="L78" title="All 4 branches missed.">                + (getClientSecret() != null &amp;&amp; !getClientSecret().isEmpty() ? &quot;*****&quot; : &quot;NULL&quot;)</span>
    +                + &quot;, scopes=&quot;
    +<span class="nc" id="L80">                + getScopes()</span>
    +                + &quot;, useAsUsername=&quot;
    +<span class="nc" id="L82">                + getUseAsUsername()</span>
    +                + &quot;]&quot;;
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/KeycloakProvider.html b/testResult/html/stirling.software.SPDF.model.provider/KeycloakProvider.html
    new file mode 100644
    index 000000000..0f65624a1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/KeycloakProvider.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakProvider</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.provider</a> &gt; <span class="el_class">KeycloakProvider</span></div><h1>KeycloakProvider</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">61 of 61</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">16</td><td class="ctr2">16</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a2"><a href="KeycloakProvider.java.html#L47" class="el_method">getScopes()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a4"><a href="KeycloakProvider.java.html#L60" class="el_method">toString()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="KeycloakProvider.java.html#L22" class="el_method">KeycloakProvider(String, String, String, Collection, UsernameAttribute)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="13" alt="13"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="KeycloakProvider.java.html#L37" class="el_method">getName()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="KeycloakProvider.java.html#L42" class="el_method">getClientName()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/KeycloakProvider.java.html b/testResult/html/stirling.software.SPDF.model.provider/KeycloakProvider.java.html
    new file mode 100644
    index 000000000..61918e7aa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/KeycloakProvider.java.html
    @@ -0,0 +1,73 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>KeycloakProvider.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.provider</a> &gt; <span class="el_source">KeycloakProvider.java</span></div><h1>KeycloakProvider.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.provider;
    +
    +import java.util.ArrayList;
    +import java.util.Collection;
    +
    +import lombok.NoArgsConstructor;
    +
    +import stirling.software.SPDF.model.UsernameAttribute;
    +
    +@NoArgsConstructor
    +public class KeycloakProvider extends Provider {
    +
    +    private static final String NAME = &quot;keycloak&quot;;
    +    private static final String CLIENT_NAME = &quot;Keycloak&quot;;
    +
    +    public KeycloakProvider(
    +            String issuer,
    +            String clientId,
    +            String clientSecret,
    +            Collection&lt;String&gt; scopes,
    +            UsernameAttribute useAsUsername) {
    +<span class="nc" id="L22">        super(</span>
    +                issuer,
    +                NAME,
    +                CLIENT_NAME,
    +                clientId,
    +                clientSecret,
    +                scopes,
    +                useAsUsername,
    +                null,
    +                null,
    +                null);
    +<span class="nc" id="L33">    }</span>
    +
    +    @Override
    +    public String getName() {
    +<span class="nc" id="L37">        return NAME;</span>
    +    }
    +
    +    @Override
    +    public String getClientName() {
    +<span class="nc" id="L42">        return CLIENT_NAME;</span>
    +    }
    +
    +    @Override
    +    public Collection&lt;String&gt; getScopes() {
    +<span class="nc" id="L47">        Collection&lt;String&gt; scopes = super.getScopes();</span>
    +
    +<span class="nc bnc" id="L49" title="All 4 branches missed.">        if (scopes == null || scopes.isEmpty()) {</span>
    +<span class="nc" id="L50">            scopes = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L51">            scopes.add(&quot;profile&quot;);</span>
    +<span class="nc" id="L52">            scopes.add(&quot;email&quot;);</span>
    +        }
    +
    +<span class="nc" id="L55">        return scopes;</span>
    +    }
    +
    +    @Override
    +    public String toString() {
    +<span class="nc" id="L60">        return &quot;Keycloak [issuer=&quot;</span>
    +<span class="nc" id="L61">                + getIssuer()</span>
    +                + &quot;, clientId=&quot;
    +<span class="nc" id="L63">                + getClientId()</span>
    +                + &quot;, clientSecret=&quot;
    +<span class="nc bnc" id="L65" title="All 4 branches missed.">                + (getClientSecret() != null &amp;&amp; !getClientSecret().isBlank() ? &quot;*****&quot; : &quot;NULL&quot;)</span>
    +                + &quot;, scopes=&quot;
    +<span class="nc" id="L67">                + getScopes()</span>
    +                + &quot;, useAsUsername=&quot;
    +<span class="nc" id="L69">                + getUseAsUsername()</span>
    +                + &quot;]&quot;;
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/Provider.html b/testResult/html/stirling.software.SPDF.model.provider/Provider.html
    new file mode 100644
    index 000000000..d0d68c92c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/Provider.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Provider</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model.provider</a> &gt; <span class="el_class">Provider</span></div><h1>Provider</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">179 of 179</td><td class="ctr2">0%</td><td class="bar">22 of 22</td><td class="ctr2">0%</td><td class="ctr1">19</td><td class="ctr2">19</td><td class="ctr1">40</td><td class="ctr2">40</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a0"><a href="Provider.java.html#L42" class="el_method">Provider(String, String, String, String, String, Collection, UsernameAttribute, String, String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="45" alt="45"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h0">13</td><td class="ctr2" id="i0">13</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="Provider.java.html#L81" class="el_method">validateKeycloakUsernameAttribute(UsernameAttribute)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="24" alt="24"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="Provider.java.html#L92" class="el_method">validateGoogleUsernameAttribute(UsernameAttribute)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="24" alt="24"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="Provider.java.html#L103" class="el_method">validateGitHubUsernameAttribute(UsernameAttribute)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="24" alt="24"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h5">4</td><td class="ctr2" id="i5">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="Provider.java.html#L115" class="el_method">toString()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="24" alt="24"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h1">7</td><td class="ctr2" id="i1">7</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="Provider.java.html#L63" class="el_method">validateUsernameAttribute(UsernameAttribute)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="23" alt="23"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a1"><a href="Provider.java.html#L57" class="el_method">setScopes(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="15" alt="15"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/Provider.java.html b/testResult/html/stirling.software.SPDF.model.provider/Provider.java.html
    new file mode 100644
    index 000000000..984846379
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/Provider.java.html
    @@ -0,0 +1,130 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Provider.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model.provider</a> &gt; <span class="el_source">Provider.java</span></div><h1>Provider.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model.provider;
    +
    +import static stirling.software.SPDF.model.UsernameAttribute.EMAIL;
    +
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collection;
    +
    +import lombok.Data;
    +import lombok.NoArgsConstructor;
    +
    +import stirling.software.SPDF.model.UsernameAttribute;
    +import stirling.software.SPDF.model.exception.UnsupportedUsernameAttribute;
    +
    +@Data
    +@NoArgsConstructor
    +public class Provider {
    +
    +    public static final String EXCEPTION_MESSAGE = &quot;The attribute %s is not supported for %s.&quot;;
    +
    +    private String issuer;
    +    private String name;
    +    private String clientName;
    +    private String clientId;
    +    private String clientSecret;
    +    private Collection&lt;String&gt; scopes;
    +    private UsernameAttribute useAsUsername;
    +    private String authorizationUri;
    +    private String tokenUri;
    +    private String userInfoUri;
    +
    +    public Provider(
    +            String issuer,
    +            String name,
    +            String clientName,
    +            String clientId,
    +            String clientSecret,
    +            Collection&lt;String&gt; scopes,
    +            UsernameAttribute useAsUsername,
    +            String authorizationUri,
    +            String tokenUri,
    +<span class="nc" id="L42">            String userInfoUri) {</span>
    +<span class="nc" id="L43">        this.issuer = issuer;</span>
    +<span class="nc" id="L44">        this.name = name;</span>
    +<span class="nc" id="L45">        this.clientName = clientName;</span>
    +<span class="nc" id="L46">        this.clientId = clientId;</span>
    +<span class="nc" id="L47">        this.clientSecret = clientSecret;</span>
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">        this.scopes = scopes == null ? new ArrayList&lt;&gt;() : scopes;</span>
    +<span class="nc" id="L49">        this.useAsUsername =</span>
    +<span class="nc bnc" id="L50" title="All 2 branches missed.">                useAsUsername != null ? validateUsernameAttribute(useAsUsername) : EMAIL;</span>
    +<span class="nc" id="L51">        this.authorizationUri = authorizationUri;</span>
    +<span class="nc" id="L52">        this.tokenUri = tokenUri;</span>
    +<span class="nc" id="L53">        this.userInfoUri = userInfoUri;</span>
    +<span class="nc" id="L54">    }</span>
    +
    +    public void setScopes(String scopes) {
    +<span class="nc bnc" id="L57" title="All 4 branches missed.">        if (scopes != null &amp;&amp; !scopes.isBlank()) {</span>
    +<span class="nc" id="L58">            this.scopes = Arrays.stream(scopes.split(&quot;,&quot;)).map(String::trim).toList();</span>
    +        }
    +<span class="nc" id="L60">    }</span>
    +
    +    private UsernameAttribute validateUsernameAttribute(UsernameAttribute usernameAttribute) {
    +<span class="nc bnc" id="L63" title="All 4 branches missed.">        switch (name) {</span>
    +            case &quot;google&quot; -&gt; {
    +<span class="nc" id="L65">                return validateGoogleUsernameAttribute(usernameAttribute);</span>
    +            }
    +            case &quot;github&quot; -&gt; {
    +<span class="nc" id="L68">                return validateGitHubUsernameAttribute(usernameAttribute);</span>
    +            }
    +            case &quot;keycloak&quot; -&gt; {
    +<span class="nc" id="L71">                return validateKeycloakUsernameAttribute(usernameAttribute);</span>
    +            }
    +            default -&gt; {
    +<span class="nc" id="L74">                return usernameAttribute;</span>
    +            }
    +        }
    +    }
    +
    +    private UsernameAttribute validateKeycloakUsernameAttribute(
    +            UsernameAttribute usernameAttribute) {
    +<span class="nc bnc" id="L81" title="All 2 branches missed.">        switch (usernameAttribute) {</span>
    +            case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -&gt; {
    +<span class="nc" id="L83">                return usernameAttribute;</span>
    +            }
    +            default -&gt;
    +<span class="nc" id="L86">                    throw new UnsupportedUsernameAttribute(</span>
    +<span class="nc" id="L87">                            String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));</span>
    +        }
    +    }
    +
    +    private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) {
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">        switch (usernameAttribute) {</span>
    +            case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -&gt; {
    +<span class="nc" id="L94">                return usernameAttribute;</span>
    +            }
    +            default -&gt;
    +<span class="nc" id="L97">                    throw new UnsupportedUsernameAttribute(</span>
    +<span class="nc" id="L98">                            String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));</span>
    +        }
    +    }
    +
    +    private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) {
    +<span class="nc bnc" id="L103" title="All 2 branches missed.">        switch (usernameAttribute) {</span>
    +            case LOGIN, EMAIL, NAME -&gt; {
    +<span class="nc" id="L105">                return usernameAttribute;</span>
    +            }
    +            default -&gt;
    +<span class="nc" id="L108">                    throw new UnsupportedUsernameAttribute(</span>
    +<span class="nc" id="L109">                            String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));</span>
    +        }
    +    }
    +
    +    @Override
    +    public String toString() {
    +<span class="nc" id="L115">        return &quot;Provider [name=&quot;</span>
    +<span class="nc" id="L116">                + getName()</span>
    +                + &quot;, clientName=&quot;
    +<span class="nc" id="L118">                + getClientName()</span>
    +                + &quot;, clientId=&quot;
    +<span class="nc" id="L120">                + getClientId()</span>
    +                + &quot;, clientSecret=&quot;
    +<span class="nc bnc" id="L122" title="All 4 branches missed.">                + (getClientSecret() != null &amp;&amp; !getClientSecret().isEmpty() ? &quot;*****&quot; : &quot;NULL&quot;)</span>
    +                + &quot;, scopes=&quot;
    +<span class="nc" id="L124">                + getScopes()</span>
    +                + &quot;, useAsUsername=&quot;
    +<span class="nc" id="L126">                + getUseAsUsername()</span>
    +                + &quot;]&quot;;
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/index.html b/testResult/html/stirling.software.SPDF.model.provider/index.html
    new file mode 100644
    index 000000000..ba8a885ce
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.provider</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.provider</span></div><h1>stirling.software.SPDF.model.provider</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">370 of 370</td><td class="ctr2">0%</td><td class="bar">48 of 48</td><td class="ctr2">0%</td><td class="ctr1">53</td><td class="ctr2">53</td><td class="ctr1">92</td><td class="ctr2">92</td><td class="ctr1">28</td><td class="ctr2">28</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a3"><a href="Provider.html" class="el_class">Provider</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="179" alt="179"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">19</td><td class="ctr2" id="g0">19</td><td class="ctr1" id="h0">40</td><td class="ctr2" id="i0">40</td><td class="ctr1" id="j2">7</td><td class="ctr2" id="k2">7</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="GitHubProvider.html" class="el_class">GitHubProvider</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="65" alt="65"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">13</td><td class="ctr2" id="g1">13</td><td class="ctr1" id="h1">18</td><td class="ctr2" id="i1">18</td><td class="ctr1" id="j0">8</td><td class="ctr2" id="k0">8</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a1"><a href="GoogleProvider.html" class="el_class">GoogleProvider</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="65" alt="65"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">12</td><td class="ctr2" id="g2">12</td><td class="ctr1" id="h2">18</td><td class="ctr2" id="i2">18</td><td class="ctr1" id="j1">8</td><td class="ctr2" id="k1">8</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a2"><a href="KeycloakProvider.html" class="el_class">KeycloakProvider</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="61" alt="61"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">9</td><td class="ctr2" id="g3">9</td><td class="ctr1" id="h3">16</td><td class="ctr2" id="i3">16</td><td class="ctr1" id="j3">5</td><td class="ctr2" id="k3">5</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model.provider/index.source.html b/testResult/html/stirling.software.SPDF.model.provider/index.source.html
    new file mode 100644
    index 000000000..95af0b184
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model.provider/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model.provider</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model.provider</span></div><h1>stirling.software.SPDF.model.provider</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">370 of 370</td><td class="ctr2">0%</td><td class="bar">48 of 48</td><td class="ctr2">0%</td><td class="ctr1">53</td><td class="ctr2">53</td><td class="ctr1">92</td><td class="ctr2">92</td><td class="ctr1">28</td><td class="ctr2">28</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a3"><a href="Provider.java.html" class="el_source">Provider.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="179" alt="179"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">19</td><td class="ctr2" id="g0">19</td><td class="ctr1" id="h0">40</td><td class="ctr2" id="i0">40</td><td class="ctr1" id="j2">7</td><td class="ctr2" id="k2">7</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="GitHubProvider.java.html" class="el_source">GitHubProvider.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="65" alt="65"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">13</td><td class="ctr2" id="g1">13</td><td class="ctr1" id="h1">18</td><td class="ctr2" id="i1">18</td><td class="ctr1" id="j0">8</td><td class="ctr2" id="k0">8</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a1"><a href="GoogleProvider.java.html" class="el_source">GoogleProvider.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="65" alt="65"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">12</td><td class="ctr2" id="g2">12</td><td class="ctr1" id="h2">18</td><td class="ctr2" id="i2">18</td><td class="ctr1" id="j1">8</td><td class="ctr2" id="k1">8</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a2"><a href="KeycloakProvider.java.html" class="el_source">KeycloakProvider.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="61" alt="61"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">9</td><td class="ctr2" id="g3">9</td><td class="ctr1" id="h3">16</td><td class="ctr2" id="i3">16</td><td class="ctr1" id="j3">5</td><td class="ctr2" id="k3">5</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApiEndpoint.html b/testResult/html/stirling.software.SPDF.model/ApiEndpoint.html
    new file mode 100644
    index 000000000..2be114ffd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApiEndpoint.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiEndpoint</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApiEndpoint</span></div><h1>ApiEndpoint</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">66 of 66</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">17</td><td class="ctr2">17</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApiEndpoint.java.html#L13" class="el_method">ApiEndpoint(String, JsonNode)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="23" alt="23"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ApiEndpoint.java.html#L26" class="el_method">areParametersValid(Map)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="109" height="10" title="21" alt="21"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="ApiEndpoint.java.html#L19" class="el_method">lambda$new$0(JsonNode)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="62" height="10" title="12" alt="12"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="ApiEndpoint.java.html#L40" class="el_method">toString()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="7" alt="7"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="ApiEndpoint.java.html#L35" class="el_method">getDescription()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApiEndpoint.java.html b/testResult/html/stirling.software.SPDF.model/ApiEndpoint.java.html
    new file mode 100644
    index 000000000..6e747f510
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApiEndpoint.java.html
    @@ -0,0 +1,43 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiEndpoint.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">ApiEndpoint.java</span></div><h1>ApiEndpoint.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.util.HashMap;
    +import java.util.Map;
    +
    +import com.fasterxml.jackson.databind.JsonNode;
    +
    +public class ApiEndpoint {
    +    private String name;
    +    private Map&lt;String, JsonNode&gt; parameters;
    +    private String description;
    +
    +<span class="nc" id="L13">    public ApiEndpoint(String name, JsonNode postNode) {</span>
    +<span class="nc" id="L14">        this.name = name;</span>
    +<span class="nc" id="L15">        this.parameters = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L16">        postNode.path(&quot;parameters&quot;)</span>
    +<span class="nc" id="L17">                .forEach(</span>
    +                        paramNode -&gt; {
    +<span class="nc" id="L19">                            String paramName = paramNode.path(&quot;name&quot;).asText();</span>
    +<span class="nc" id="L20">                            parameters.put(paramName, paramNode);</span>
    +<span class="nc" id="L21">                        });</span>
    +<span class="nc" id="L22">        this.description = postNode.path(&quot;description&quot;).asText();</span>
    +<span class="nc" id="L23">    }</span>
    +
    +    public boolean areParametersValid(Map&lt;String, Object&gt; providedParams) {
    +<span class="nc bnc" id="L26" title="All 2 branches missed.">        for (String requiredParam : parameters.keySet()) {</span>
    +<span class="nc bnc" id="L27" title="All 2 branches missed.">            if (!providedParams.containsKey(requiredParam)) {</span>
    +<span class="nc" id="L28">                return false;</span>
    +            }
    +<span class="nc" id="L30">        }</span>
    +<span class="nc" id="L31">        return true;</span>
    +    }
    +
    +    public String getDescription() {
    +<span class="nc" id="L35">        return description;</span>
    +    }
    +
    +    @Override
    +    public String toString() {
    +<span class="nc" id="L40">        return &quot;ApiEndpoint [name=&quot; + name + &quot;, parameters=&quot; + parameters + &quot;]&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApiKeyAuthenticationToken.html b/testResult/html/stirling.software.SPDF.model/ApiKeyAuthenticationToken.html
    new file mode 100644
    index 000000000..7baea0d43
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApiKeyAuthenticationToken.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiKeyAuthenticationToken</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApiKeyAuthenticationToken</span></div><h1>ApiKeyAuthenticationToken</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">49 of 49</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">19</td><td class="ctr2">19</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a1"><a href="ApiKeyAuthenticationToken.java.html#L14" class="el_method">ApiKeyAuthenticationToken(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ApiKeyAuthenticationToken.java.html#L22" class="el_method">ApiKeyAuthenticationToken(Object, String, Collection)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="ApiKeyAuthenticationToken.java.html#L40" class="el_method">setAuthenticated(boolean)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="101" height="10" title="11" alt="11"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="ApiKeyAuthenticationToken.java.html#L49" class="el_method">eraseCredentials()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="55" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="ApiKeyAuthenticationToken.java.html#L30" class="el_method">getCredentials()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a4"><a href="ApiKeyAuthenticationToken.java.html#L35" class="el_method">getPrincipal()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApiKeyAuthenticationToken.java.html b/testResult/html/stirling.software.SPDF.model/ApiKeyAuthenticationToken.java.html
    new file mode 100644
    index 000000000..137501d29
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApiKeyAuthenticationToken.java.html
    @@ -0,0 +1,53 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApiKeyAuthenticationToken.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">ApiKeyAuthenticationToken.java</span></div><h1>ApiKeyAuthenticationToken.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.util.Collection;
    +
    +import org.springframework.security.authentication.AbstractAuthenticationToken;
    +import org.springframework.security.core.GrantedAuthority;
    +
    +public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken {
    +
    +    private final Object principal;
    +    private Object credentials;
    +
    +    public ApiKeyAuthenticationToken(String apiKey) {
    +<span class="nc" id="L14">        super(null);</span>
    +<span class="nc" id="L15">        this.principal = null;</span>
    +<span class="nc" id="L16">        this.credentials = apiKey;</span>
    +<span class="nc" id="L17">        setAuthenticated(false);</span>
    +<span class="nc" id="L18">    }</span>
    +
    +    public ApiKeyAuthenticationToken(
    +            Object principal, String apiKey, Collection&lt;? extends GrantedAuthority&gt; authorities) {
    +<span class="nc" id="L22">        super(authorities);</span>
    +<span class="nc" id="L23">        this.principal = principal; // principal can be a UserDetails object</span>
    +<span class="nc" id="L24">        this.credentials = apiKey;</span>
    +<span class="nc" id="L25">        super.setAuthenticated(true); // this authentication is trusted</span>
    +<span class="nc" id="L26">    }</span>
    +
    +    @Override
    +    public Object getCredentials() {
    +<span class="nc" id="L30">        return credentials;</span>
    +    }
    +
    +    @Override
    +    public Object getPrincipal() {
    +<span class="nc" id="L35">        return principal;</span>
    +    }
    +
    +    @Override
    +    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">        if (isAuthenticated) {</span>
    +<span class="nc" id="L41">            throw new IllegalArgumentException(</span>
    +                    &quot;Cannot set this token to trusted. Use constructor which takes a GrantedAuthority list instead.&quot;);
    +        }
    +<span class="nc" id="L44">        super.setAuthenticated(false);</span>
    +<span class="nc" id="L45">    }</span>
    +
    +    @Override
    +    public void eraseCredentials() {
    +<span class="nc" id="L49">        super.eraseCredentials();</span>
    +<span class="nc" id="L50">        credentials = null;</span>
    +<span class="nc" id="L51">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Driver.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Driver.html
    new file mode 100644
    index 000000000..07c071617
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Driver.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Driver</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Driver</span></div><h1>ApplicationProperties.Driver</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">49 of 49</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="ApplicationProperties.java.html#L330" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="31" alt="31"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="ApplicationProperties.java.html#L344" class="el_method">toString()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="10" alt="10"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="ApplicationProperties.java.html#L338" class="el_method">ApplicationProperties.Driver(String, int, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="8" alt="8"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$EnterpriseEdition$CustomMetadata.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$EnterpriseEdition$CustomMetadata.html
    new file mode 100644
    index 000000000..a0760e1bd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$EnterpriseEdition$CustomMetadata.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.EnterpriseEdition.CustomMetadata</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.EnterpriseEdition.CustomMetadata</span></div><h1>ApplicationProperties.EnterpriseEdition.CustomMetadata</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">26 of 26</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApplicationProperties.java.html#L413" class="el_method">getCreator()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L417" class="el_method">getProducer()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$CustomMetadata.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$CustomMetadata.html
    new file mode 100644
    index 000000000..2d026d516
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$CustomMetadata.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Premium.ProFeatures.CustomMetadata</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Premium.ProFeatures.CustomMetadata</span></div><h1>ApplicationProperties.Premium.ProFeatures.CustomMetadata</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">26 of 26</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApplicationProperties.java.html#L444" class="el_method">getCreator()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L448" class="el_method">getProducer()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$GoogleDrive.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$GoogleDrive.html
    new file mode 100644
    index 000000000..28438f978
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Premium$ProFeatures$GoogleDrive.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Premium.ProFeatures.GoogleDrive</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Premium.ProFeatures.GoogleDrive</span></div><h1>ApplicationProperties.Premium.ProFeatures.GoogleDrive</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">39 of 39</td><td class="ctr2">0%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="ApplicationProperties.java.html#L462" class="el_method">getClientId()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ApplicationProperties.java.html#L466" class="el_method">getApiKey()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L470" class="el_method">getAppId()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$SessionLimit.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$SessionLimit.html
    new file mode 100644
    index 000000000..1f2db9526
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$SessionLimit.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.ProcessExecutor.SessionLimit</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.ProcessExecutor.SessionLimit</span></div><h1>ApplicationProperties.ProcessExecutor.SessionLimit</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">59 of 64</td><td class="ctr2">7%</td><td class="bar">15 of 16</td><td class="ctr2">6%</td><td class="ctr1">15</td><td class="ctr2">16</td><td class="ctr1">7</td><td class="ctr2">8</td><td class="ctr1">7</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a5"><a href="ApplicationProperties.java.html#L504" class="el_method">getQpdfSessionLimit()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a6"><a href="ApplicationProperties.java.html#L508" class="el_method">getTesseractSessionLimit()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="ApplicationProperties.java.html#L512" class="el_method">getLibreOfficeSessionLimit()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="ApplicationProperties.java.html#L520" class="el_method">getPythonOpenCvSessionLimit()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a7"><a href="ApplicationProperties.java.html#L524" class="el_method">getWeasyPrintSessionLimit()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L528" class="el_method">getInstallAppSessionLimit()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a0"><a href="ApplicationProperties.java.html#L532" class="el_method">getCalibreSessionLimit()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a3"><a href="ApplicationProperties.java.html#L516" class="el_method">getPdfToHtmlSessionLimit()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="3" alt="3"/><img src="../jacoco-resources/greenbar.gif" width="75" height="10" title="5" alt="5"/></td><td class="ctr2" id="c0">62%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="1" alt="1"/><img src="../jacoco-resources/greenbar.gif" width="60" height="10" title="1" alt="1"/></td><td class="ctr2" id="e0">50%</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h7">0</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">0</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$TimeoutMinutes.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$TimeoutMinutes.html
    new file mode 100644
    index 000000000..4294d9a23
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$ProcessExecutor$TimeoutMinutes.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.ProcessExecutor.TimeoutMinutes</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.ProcessExecutor.TimeoutMinutes</span></div><h1>ApplicationProperties.ProcessExecutor.TimeoutMinutes</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">73 of 80</td><td class="ctr2">8%</td><td class="bar">15 of 16</td><td class="ctr2">6%</td><td class="ctr1">15</td><td class="ctr2">16</td><td class="ctr1">7</td><td class="ctr2">8</td><td class="ctr1">7</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a6"><a href="ApplicationProperties.java.html#L548" class="el_method">getTesseractTimeoutMinutes()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="ApplicationProperties.java.html#L552" class="el_method">getQpdfTimeoutMinutes()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="ApplicationProperties.java.html#L556" class="el_method">getLibreOfficeTimeoutMinutes()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="ApplicationProperties.java.html#L564" class="el_method">getPythonOpenCvTimeoutMinutes()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a7"><a href="ApplicationProperties.java.html#L568" class="el_method">getWeasyPrintTimeoutMinutes()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L572" class="el_method">getInstallAppTimeoutMinutes()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a0"><a href="ApplicationProperties.java.html#L576" class="el_method">getCalibreTimeoutMinutes()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a3"><a href="ApplicationProperties.java.html#L560" class="el_method">getPdfToHtmlTimeoutMinutes()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="3" alt="3"/><img src="../jacoco-resources/greenbar.gif" width="84" height="10" title="7" alt="7"/></td><td class="ctr2" id="c0">70%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="1" alt="1"/><img src="../jacoco-resources/greenbar.gif" width="60" height="10" title="1" alt="1"/></td><td class="ctr2" id="e0">50%</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h7">0</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">0</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$LoginMethods.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$LoginMethods.html
    new file mode 100644
    index 000000000..8311957bc
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$LoginMethods.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Security.LoginMethods</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Security.LoginMethods</span></div><h1>ApplicationProperties.Security.LoginMethods</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="ApplicationProperties.java.html#L120" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="31" alt="31"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="ApplicationProperties.java.html#L128" class="el_method">ApplicationProperties.Security.LoginMethods(String, int, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="ApplicationProperties.java.html#L134" class="el_method">toString()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2$Client.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2$Client.html
    new file mode 100644
    index 000000000..c864ba88a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2$Client.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Security.OAUTH2.Client</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Security.OAUTH2.Client</span></div><h1>ApplicationProperties.Security.OAUTH2.Client</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">25 of 25</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApplicationProperties.java.html#L263" class="el_method">get(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="25" alt="25"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2.html
    new file mode 100644
    index 000000000..71b68bd48
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$OAUTH2.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Security.OAUTH2</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Security.OAUTH2</span></div><h1>ApplicationProperties.Security.OAUTH2</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">57 of 57</td><td class="ctr2">0%</td><td class="bar">18 of 18</td><td class="ctr2">0%</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApplicationProperties.java.html#L249" class="el_method">isSettingsValid()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="ApplicationProperties.java.html#L235" class="el_method">setScopes(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="14" alt="14"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="ApplicationProperties.java.html#L241" class="el_method">isValid(String, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="10" alt="10"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L245" class="el_method">isValid(Collection, String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="9" alt="9"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$SAML2.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$SAML2.html
    new file mode 100644
    index 000000000..2ed2e70da
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security$SAML2.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Security.SAML2</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Security.SAML2</span></div><h1>ApplicationProperties.Security.SAML2</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">113 of 131</td><td class="ctr2">13%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">10</td><td class="ctr2">11</td><td class="ctr1">21</td><td class="ctr2">26</td><td class="ctr1">4</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a2"><a href="ApplicationProperties.java.html#L179" class="el_method">getIdpMetadataUri()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="43" alt="43"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h0">10</td><td class="ctr2" id="i0">10</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a4"><a href="ApplicationProperties.java.html#L195" class="el_method">getSpCert()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="69" height="10" title="25" alt="25"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L204" class="el_method">getIdpCert()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="69" height="10" title="25" alt="25"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="ApplicationProperties.java.html#L213" class="el_method">getPrivateKey()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="55" height="10" title="20" alt="20"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i4">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="ApplicationProperties.java.html#L164" class="el_method">ApplicationProperties.Security.SAML2()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/greenbar.gif" width="50" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">0</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">0</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j4">0</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security.html
    new file mode 100644
    index 000000000..aaae226fe
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Security.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Security</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Security</span></div><h1>ApplicationProperties.Security</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">67 of 67</td><td class="ctr2">0%</td><td class="bar">20 of 20</td><td class="ctr2">0%</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="ApplicationProperties.java.html#L144" class="el_method">isOauth2Active()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="ApplicationProperties.java.html#L150" class="el_method">isSaml2Active()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="ApplicationProperties.java.html#L139" class="el_method">isUserPass()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="106" height="10" title="16" alt="16"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="ApplicationProperties.java.html#L117" class="el_method">isAltLogin()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="15" alt="15"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$System.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$System.html
    new file mode 100644
    index 000000000..720665ed7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$System.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.System</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.System</span></div><h1>ApplicationProperties.System</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">11 of 11</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApplicationProperties.java.html#L295" class="el_method">isAnalyticsEnabled()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="11" alt="11"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Ui.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Ui.html
    new file mode 100644
    index 000000000..2810c3a97
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties$Ui.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.Ui</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties.Ui</span></div><h1>ApplicationProperties.Ui</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">39 of 39</td><td class="ctr2">0%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApplicationProperties.java.html#L361" class="el_method">getAppName()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="ApplicationProperties.java.html#L365" class="el_method">getHomeDescription()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L371" class="el_method">getAppNameNavbar()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="13" alt="13"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties.html
    new file mode 100644
    index 000000000..c619fd3b2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">ApplicationProperties</span></div><h1>ApplicationProperties</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">56 of 60</td><td class="ctr2">6%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">3</td><td class="ctr2">4</td><td class="ctr1">14</td><td class="ctr2">15</td><td class="ctr1">1</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ApplicationProperties.java.html#L54" class="el_method">dynamicYamlPropertySource(ConfigurableEnvironment)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="56" alt="56"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">14</td><td class="ctr2" id="i0">14</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ApplicationProperties.java.html#L48" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/greenbar.gif" width="8" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">0</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">0</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/ApplicationProperties.java.html b/testResult/html/stirling.software.SPDF.model/ApplicationProperties.java.html
    new file mode 100644
    index 000000000..9e46bcec1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/ApplicationProperties.java.html
    @@ -0,0 +1,581 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ApplicationProperties.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">ApplicationProperties.java</span></div><h1>ApplicationProperties.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import static stirling.software.SPDF.utils.validation.Validator.*;
    +
    +import java.io.File;
    +import java.io.FileNotFoundException;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.HttpURLConnection;
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.List;
    +
    +import org.springframework.boot.context.properties.ConfigurationProperties;
    +import org.springframework.context.annotation.Bean;
    +import org.springframework.context.annotation.Configuration;
    +import org.springframework.core.Ordered;
    +import org.springframework.core.annotation.Order;
    +import org.springframework.core.env.ConfigurableEnvironment;
    +import org.springframework.core.env.PropertySource;
    +import org.springframework.core.io.ClassPathResource;
    +import org.springframework.core.io.FileSystemResource;
    +import org.springframework.core.io.Resource;
    +import org.springframework.core.io.support.EncodedResource;
    +
    +import lombok.Data;
    +import lombok.Getter;
    +import lombok.Setter;
    +import lombok.ToString;
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.config.YamlPropertySourceFactory;
    +import stirling.software.SPDF.model.exception.UnsupportedProviderException;
    +import stirling.software.SPDF.model.provider.GitHubProvider;
    +import stirling.software.SPDF.model.provider.GoogleProvider;
    +import stirling.software.SPDF.model.provider.KeycloakProvider;
    +import stirling.software.SPDF.model.provider.Provider;
    +
    +@Configuration
    +@ConfigurationProperties(prefix = &quot;&quot;)
    +@Data
    +@Order(Ordered.HIGHEST_PRECEDENCE)
    +<span class="fc" id="L48">@Slf4j</span>
    +public class ApplicationProperties {
    +
    +    @Bean
    +    public PropertySource&lt;?&gt; dynamicYamlPropertySource(ConfigurableEnvironment environment)
    +            throws IOException {
    +<span class="nc" id="L54">        String configPath = InstallationPathConfig.getSettingsPath();</span>
    +<span class="nc" id="L55">        log.debug(&quot;Attempting to load settings from: &quot; + configPath);</span>
    +
    +<span class="nc" id="L57">        File file = new File(configPath);</span>
    +<span class="nc bnc" id="L58" title="All 2 branches missed.">        if (!file.exists()) {</span>
    +<span class="nc" id="L59">            log.error(&quot;Warning: Settings file does not exist at: &quot; + configPath);</span>
    +        }
    +
    +<span class="nc" id="L62">        Resource resource = new FileSystemResource(configPath);</span>
    +<span class="nc bnc" id="L63" title="All 2 branches missed.">        if (!resource.exists()) {</span>
    +<span class="nc" id="L64">            throw new FileNotFoundException(&quot;Settings file not found at: &quot; + configPath);</span>
    +        }
    +
    +<span class="nc" id="L67">        EncodedResource encodedResource = new EncodedResource(resource);</span>
    +<span class="nc" id="L68">        PropertySource&lt;?&gt; propertySource =</span>
    +<span class="nc" id="L69">                new YamlPropertySourceFactory().createPropertySource(null, encodedResource);</span>
    +<span class="nc" id="L70">        environment.getPropertySources().addFirst(propertySource);</span>
    +
    +<span class="nc" id="L72">        log.debug(&quot;Loaded properties: &quot; + propertySource.getSource());</span>
    +
    +<span class="nc" id="L74">        return propertySource;</span>
    +    }
    +
    +    private Legal legal = new Legal();
    +    private Security security = new Security();
    +    private System system = new System();
    +    private Ui ui = new Ui();
    +    private Endpoints endpoints = new Endpoints();
    +    private Metrics metrics = new Metrics();
    +    private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
    +
    +    private Premium premium = new Premium();
    +    private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
    +    private AutoPipeline autoPipeline = new AutoPipeline();
    +    private ProcessExecutor processExecutor = new ProcessExecutor();
    +
    +    @Data
    +    public static class AutoPipeline {
    +        private String outputFolder;
    +    }
    +
    +    @Data
    +    public static class Legal {
    +        private String termsAndConditions;
    +        private String privacyPolicy;
    +        private String accessibilityStatement;
    +        private String cookiePolicy;
    +        private String impressum;
    +    }
    +
    +    @Data
    +    public static class Security {
    +        private Boolean enableLogin;
    +        private Boolean csrfDisabled;
    +        private InitialLogin initialLogin = new InitialLogin();
    +        private OAUTH2 oauth2 = new OAUTH2();
    +        private SAML2 saml2 = new SAML2();
    +        private int loginAttemptCount;
    +        private long loginResetTimeMinutes;
    +        private String loginMethod = &quot;all&quot;;
    +        private String customGlobalAPIKey;
    +
    +        public Boolean isAltLogin() {
    +<span class="nc bnc" id="L117" title="All 4 branches missed.">            return saml2.getEnabled() || oauth2.getEnabled();</span>
    +        }
    +
    +<span class="nc" id="L120">        public enum LoginMethods {</span>
    +<span class="nc" id="L121">            ALL(&quot;all&quot;),</span>
    +<span class="nc" id="L122">            NORMAL(&quot;normal&quot;),</span>
    +<span class="nc" id="L123">            OAUTH2(&quot;oauth2&quot;),</span>
    +<span class="nc" id="L124">            SAML2(&quot;saml2&quot;);</span>
    +
    +            private String method;
    +
    +<span class="nc" id="L128">            LoginMethods(String method) {</span>
    +<span class="nc" id="L129">                this.method = method;</span>
    +<span class="nc" id="L130">            }</span>
    +
    +            @Override
    +            public String toString() {
    +<span class="nc" id="L134">                return method;</span>
    +            }
    +        }
    +
    +        public boolean isUserPass() {
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">            return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())</span>
    +<span class="nc bnc" id="L140" title="All 2 branches missed.">                    || loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));</span>
    +        }
    +
    +        public boolean isOauth2Active() {
    +<span class="nc bnc" id="L144" title="All 2 branches missed.">            return (oauth2 != null</span>
    +<span class="nc bnc" id="L145" title="All 2 branches missed.">                    &amp;&amp; oauth2.getEnabled()</span>
    +<span class="nc bnc" id="L146" title="All 2 branches missed.">                    &amp;&amp; !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));</span>
    +        }
    +
    +        public boolean isSaml2Active() {
    +<span class="nc bnc" id="L150" title="All 2 branches missed.">            return (saml2 != null</span>
    +<span class="nc bnc" id="L151" title="All 2 branches missed.">                    &amp;&amp; saml2.getEnabled()</span>
    +<span class="nc bnc" id="L152" title="All 2 branches missed.">                    &amp;&amp; !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));</span>
    +        }
    +
    +        @Data
    +        public static class InitialLogin {
    +            private String username;
    +            @ToString.Exclude private String password;
    +        }
    +
    +        @Getter
    +        @Setter
    +        @ToString
    +<span class="fc" id="L164">        public static class SAML2 {</span>
    +            private String provider;
    +<span class="fc" id="L166">            private Boolean enabled = false;</span>
    +<span class="fc" id="L167">            private Boolean autoCreateUser = false;</span>
    +<span class="fc" id="L168">            private Boolean blockRegistration = false;</span>
    +<span class="fc" id="L169">            private String registrationId = &quot;stirling&quot;;</span>
    +            @ToString.Exclude private String idpMetadataUri;
    +            private String idpSingleLogoutUrl;
    +            private String idpSingleLoginUrl;
    +            private String idpIssuer;
    +            private String idpCert;
    +            @ToString.Exclude private String privateKey;
    +            @ToString.Exclude private String spCert;
    +
    +            public InputStream getIdpMetadataUri() throws IOException {
    +<span class="nc bnc" id="L179" title="All 2 branches missed.">                if (idpMetadataUri.startsWith(&quot;classpath:&quot;)) {</span>
    +<span class="nc" id="L180">                    return new ClassPathResource(idpMetadataUri.substring(&quot;classpath&quot;.length()))</span>
    +<span class="nc" id="L181">                            .getInputStream();</span>
    +                }
    +                try {
    +<span class="nc" id="L184">                    URI uri = new URI(idpMetadataUri);</span>
    +<span class="nc" id="L185">                    URL url = uri.toURL();</span>
    +<span class="nc" id="L186">                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();</span>
    +<span class="nc" id="L187">                    connection.setRequestMethod(&quot;GET&quot;);</span>
    +<span class="nc" id="L188">                    return connection.getInputStream();</span>
    +<span class="nc" id="L189">                } catch (URISyntaxException e) {</span>
    +<span class="nc" id="L190">                    throw new IOException(&quot;Invalid URI format: &quot; + idpMetadataUri, e);</span>
    +                }
    +            }
    +
    +            public Resource getSpCert() {
    +<span class="nc bnc" id="L195" title="All 2 branches missed.">                if (spCert == null) return null;</span>
    +<span class="nc bnc" id="L196" title="All 2 branches missed.">                if (spCert.startsWith(&quot;classpath:&quot;)) {</span>
    +<span class="nc" id="L197">                    return new ClassPathResource(spCert.substring(&quot;classpath:&quot;.length()));</span>
    +                } else {
    +<span class="nc" id="L199">                    return new FileSystemResource(spCert);</span>
    +                }
    +            }
    +
    +            public Resource getIdpCert() {
    +<span class="nc bnc" id="L204" title="All 2 branches missed.">                if (idpCert == null) return null;</span>
    +<span class="nc bnc" id="L205" title="All 2 branches missed.">                if (idpCert.startsWith(&quot;classpath:&quot;)) {</span>
    +<span class="nc" id="L206">                    return new ClassPathResource(idpCert.substring(&quot;classpath:&quot;.length()));</span>
    +                } else {
    +<span class="nc" id="L208">                    return new FileSystemResource(idpCert);</span>
    +                }
    +            }
    +
    +            public Resource getPrivateKey() {
    +<span class="nc bnc" id="L213" title="All 2 branches missed.">                if (privateKey.startsWith(&quot;classpath:&quot;)) {</span>
    +<span class="nc" id="L214">                    return new ClassPathResource(privateKey.substring(&quot;classpath:&quot;.length()));</span>
    +                } else {
    +<span class="nc" id="L216">                    return new FileSystemResource(privateKey);</span>
    +                }
    +            }
    +        }
    +
    +        @Data
    +        public static class OAUTH2 {
    +            private Boolean enabled = false;
    +            private String issuer;
    +            private String clientId;
    +            @ToString.Exclude private String clientSecret;
    +            private Boolean autoCreateUser = false;
    +            private Boolean blockRegistration = false;
    +            private String useAsUsername;
    +            private Collection&lt;String&gt; scopes = new ArrayList&lt;&gt;();
    +            private String provider;
    +            private Client client = new Client();
    +
    +            public void setScopes(String scopes) {
    +<span class="nc" id="L235">                List&lt;String&gt; scopesList =</span>
    +<span class="nc" id="L236">                        Arrays.stream(scopes.split(&quot;,&quot;)).map(String::trim).toList();</span>
    +<span class="nc" id="L237">                this.scopes.addAll(scopesList);</span>
    +<span class="nc" id="L238">            }</span>
    +
    +            protected boolean isValid(String value, String name) {
    +<span class="nc bnc" id="L241" title="All 4 branches missed.">                return value != null &amp;&amp; !value.trim().isEmpty();</span>
    +            }
    +
    +            protected boolean isValid(Collection&lt;String&gt; value, String name) {
    +<span class="nc bnc" id="L245" title="All 4 branches missed.">                return value != null &amp;&amp; !value.isEmpty();</span>
    +            }
    +
    +            public boolean isSettingsValid() {
    +<span class="nc bnc" id="L249" title="All 2 branches missed.">                return !isStringEmpty(this.getIssuer())</span>
    +<span class="nc bnc" id="L250" title="All 2 branches missed.">                        &amp;&amp; !isStringEmpty(this.getClientId())</span>
    +<span class="nc bnc" id="L251" title="All 2 branches missed.">                        &amp;&amp; !isStringEmpty(this.getClientSecret())</span>
    +<span class="nc bnc" id="L252" title="All 2 branches missed.">                        &amp;&amp; !isCollectionEmpty(this.getScopes())</span>
    +<span class="nc bnc" id="L253" title="All 2 branches missed.">                        &amp;&amp; !isStringEmpty(this.getUseAsUsername());</span>
    +            }
    +
    +            @Data
    +            public static class Client {
    +                private GoogleProvider google = new GoogleProvider();
    +                private GitHubProvider github = new GitHubProvider();
    +                private KeycloakProvider keycloak = new KeycloakProvider();
    +
    +                public Provider get(String registrationId) throws UnsupportedProviderException {
    +<span class="nc bnc" id="L263" title="All 4 branches missed.">                    return switch (registrationId.toLowerCase()) {</span>
    +<span class="nc" id="L264">                        case &quot;google&quot; -&gt; getGoogle();</span>
    +<span class="nc" id="L265">                        case &quot;github&quot; -&gt; getGithub();</span>
    +<span class="nc" id="L266">                        case &quot;keycloak&quot; -&gt; getKeycloak();</span>
    +                        default -&gt;
    +<span class="nc" id="L268">                                throw new UnsupportedProviderException(</span>
    +                                        &quot;Logout from the provider &quot;
    +                                                + registrationId
    +                                                + &quot; is not supported. &quot;
    +                                                + &quot;Report it at https://github.com/Stirling-Tools/Stirling-PDF/issues&quot;);
    +                    };
    +                }
    +            }
    +        }
    +    }
    +
    +    @Data
    +    public static class System {
    +        private String defaultLocale;
    +        private Boolean googlevisibility;
    +        private boolean showUpdate;
    +        private Boolean showUpdateOnlyAdmin;
    +        private boolean customHTMLFiles;
    +        private String tessdataDir;
    +        private Boolean enableAlphaFunctionality;
    +        private Boolean enableAnalytics;
    +        private Datasource datasource;
    +        private Boolean disableSanitize;
    +        private Boolean enableUrlToPDF;
    +        private CustomPaths customPaths = new CustomPaths();
    +
    +        public boolean isAnalyticsEnabled() {
    +<span class="nc bnc" id="L295" title="All 4 branches missed.">            return this.getEnableAnalytics() != null &amp;&amp; this.getEnableAnalytics();</span>
    +        }
    +    }
    +
    +    @Data
    +    public static class CustomPaths {
    +        private Pipeline pipeline = new Pipeline();
    +        private Operations operations = new Operations();
    +
    +        @Data
    +        public static class Pipeline {
    +            private String watchedFoldersDir;
    +            private String finishedFoldersDir;
    +            private String webUIConfigsDir;
    +        }
    +
    +        @Data
    +        public static class Operations {
    +            private String weasyprint;
    +            private String unoconvert;
    +        }
    +    }
    +
    +    @Data
    +    public static class Datasource {
    +        private boolean enableCustomDatabase;
    +        private String customDatabaseUrl;
    +        private String type;
    +        private String hostName;
    +        private Integer port;
    +        private String name;
    +        private String username;
    +        @ToString.Exclude private String password;
    +    }
    +
    +<span class="nc" id="L330">    public enum Driver {</span>
    +<span class="nc" id="L331">        H2(&quot;h2&quot;),</span>
    +<span class="nc" id="L332">        POSTGRESQL(&quot;postgresql&quot;),</span>
    +<span class="nc" id="L333">        ORACLE(&quot;oracle&quot;),</span>
    +<span class="nc" id="L334">        MYSQL(&quot;mysql&quot;);</span>
    +
    +        private final String driverName;
    +
    +<span class="nc" id="L338">        Driver(String driverName) {</span>
    +<span class="nc" id="L339">            this.driverName = driverName;</span>
    +<span class="nc" id="L340">        }</span>
    +
    +        @Override
    +        public String toString() {
    +<span class="nc" id="L344">            return &quot;&quot;&quot;</span>
    +                    Driver {
    +                      driverName='%s'
    +                    }
    +                    &quot;&quot;&quot;
    +<span class="nc" id="L349">                    .formatted(driverName);</span>
    +        }
    +    }
    +
    +    @Data
    +    public static class Ui {
    +        private String appName;
    +        private String homeDescription;
    +        private String appNameNavbar;
    +        private List&lt;String&gt; languages;
    +
    +        public String getAppName() {
    +<span class="nc bnc" id="L361" title="All 4 branches missed.">            return appName != null &amp;&amp; appName.trim().length() &gt; 0 ? appName : null;</span>
    +        }
    +
    +        public String getHomeDescription() {
    +<span class="nc bnc" id="L365" title="All 4 branches missed.">            return homeDescription != null &amp;&amp; homeDescription.trim().length() &gt; 0</span>
    +<span class="nc" id="L366">                    ? homeDescription</span>
    +<span class="nc" id="L367">                    : null;</span>
    +        }
    +
    +        public String getAppNameNavbar() {
    +<span class="nc bnc" id="L371" title="All 4 branches missed.">            return appNameNavbar != null &amp;&amp; appNameNavbar.trim().length() &gt; 0</span>
    +<span class="nc" id="L372">                    ? appNameNavbar</span>
    +<span class="nc" id="L373">                    : null;</span>
    +        }
    +    }
    +
    +    @Data
    +    public static class Endpoints {
    +        private List&lt;String&gt; toRemove;
    +        private List&lt;String&gt; groupsToRemove;
    +    }
    +
    +    @Data
    +    public static class Metrics {
    +        private Boolean enabled;
    +    }
    +
    +    @Data
    +    public static class AutomaticallyGenerated {
    +        @ToString.Exclude private String key;
    +        private String UUID;
    +        private String appVersion;
    +    }
    +
    +    // TODO: Remove post migration
    +    @Data
    +    @Deprecated(since = &quot;0.45.0&quot;)
    +    public static class EnterpriseEdition {
    +        private boolean enabled;
    +        @ToString.Exclude private String key;
    +        private int maxUsers;
    +        private boolean ssoAutoLogin;
    +        private CustomMetadata customMetadata = new CustomMetadata();
    +
    +        @Data
    +        public static class CustomMetadata {
    +            private boolean autoUpdateMetadata;
    +            private String author;
    +            private String creator;
    +            private String producer;
    +
    +            public String getCreator() {
    +<span class="nc bnc" id="L413" title="All 4 branches missed.">                return creator == null || creator.trim().isEmpty() ? &quot;Stirling-PDF&quot; : creator;</span>
    +            }
    +
    +            public String getProducer() {
    +<span class="nc bnc" id="L417" title="All 4 branches missed.">                return producer == null || producer.trim().isEmpty() ? &quot;Stirling-PDF&quot; : producer;</span>
    +            }
    +        }
    +    }
    +
    +    @Data
    +    public static class Premium {
    +        private boolean enabled;
    +        @ToString.Exclude private String key;
    +        private int maxUsers;
    +        private ProFeatures proFeatures = new ProFeatures();
    +        private EnterpriseFeatures enterpriseFeatures = new EnterpriseFeatures();
    +
    +        @Data
    +        public static class ProFeatures {
    +            private boolean ssoAutoLogin;
    +            private CustomMetadata customMetadata = new CustomMetadata();
    +            private GoogleDrive googleDrive = new GoogleDrive();
    +
    +            @Data
    +            public static class CustomMetadata {
    +                private boolean autoUpdateMetadata;
    +                private String author;
    +                private String creator;
    +                private String producer;
    +
    +                public String getCreator() {
    +<span class="nc bnc" id="L444" title="All 4 branches missed.">                    return creator == null || creator.trim().isEmpty() ? &quot;Stirling-PDF&quot; : creator;</span>
    +                }
    +
    +                public String getProducer() {
    +<span class="nc bnc" id="L448" title="All 4 branches missed.">                    return producer == null || producer.trim().isEmpty()</span>
    +<span class="nc" id="L449">                            ? &quot;Stirling-PDF&quot;</span>
    +<span class="nc" id="L450">                            : producer;</span>
    +                }
    +            }
    +
    +            @Data
    +            public static class GoogleDrive {
    +                private boolean enabled;
    +                private String clientId;
    +                private String apiKey;
    +                private String appId;
    +
    +                public String getClientId() {
    +<span class="nc bnc" id="L462" title="All 4 branches missed.">                    return clientId == null || clientId.trim().isEmpty() ? &quot;&quot; : clientId;</span>
    +                }
    +
    +                public String getApiKey() {
    +<span class="nc bnc" id="L466" title="All 4 branches missed.">                    return apiKey == null || apiKey.trim().isEmpty() ? &quot;&quot; : apiKey;</span>
    +                }
    +
    +                public String getAppId() {
    +<span class="nc bnc" id="L470" title="All 4 branches missed.">                    return appId == null || appId.trim().isEmpty() ? &quot;&quot; : appId;</span>
    +                }
    +            }
    +        }
    +
    +        @Data
    +        public static class EnterpriseFeatures {
    +            private PersistentMetrics persistentMetrics = new PersistentMetrics();
    +
    +            @Data
    +            public static class PersistentMetrics {
    +                private boolean enabled;
    +                private int retentionDays;
    +            }
    +        }
    +    }
    +
    +    @Data
    +    public static class ProcessExecutor {
    +        private SessionLimit sessionLimit = new SessionLimit();
    +        private TimeoutMinutes timeoutMinutes = new TimeoutMinutes();
    +
    +        @Data
    +        public static class SessionLimit {
    +            private int libreOfficeSessionLimit;
    +            private int pdfToHtmlSessionLimit;
    +            private int pythonOpenCvSessionLimit;
    +            private int weasyPrintSessionLimit;
    +            private int installAppSessionLimit;
    +            private int calibreSessionLimit;
    +            private int qpdfSessionLimit;
    +            private int tesseractSessionLimit;
    +
    +            public int getQpdfSessionLimit() {
    +<span class="nc bnc" id="L504" title="All 2 branches missed.">                return qpdfSessionLimit &gt; 0 ? qpdfSessionLimit : 2;</span>
    +            }
    +
    +            public int getTesseractSessionLimit() {
    +<span class="nc bnc" id="L508" title="All 2 branches missed.">                return tesseractSessionLimit &gt; 0 ? tesseractSessionLimit : 1;</span>
    +            }
    +
    +            public int getLibreOfficeSessionLimit() {
    +<span class="nc bnc" id="L512" title="All 2 branches missed.">                return libreOfficeSessionLimit &gt; 0 ? libreOfficeSessionLimit : 1;</span>
    +            }
    +
    +            public int getPdfToHtmlSessionLimit() {
    +<span class="pc bpc" id="L516" title="1 of 2 branches missed.">                return pdfToHtmlSessionLimit &gt; 0 ? pdfToHtmlSessionLimit : 1;</span>
    +            }
    +
    +            public int getPythonOpenCvSessionLimit() {
    +<span class="nc bnc" id="L520" title="All 2 branches missed.">                return pythonOpenCvSessionLimit &gt; 0 ? pythonOpenCvSessionLimit : 8;</span>
    +            }
    +
    +            public int getWeasyPrintSessionLimit() {
    +<span class="nc bnc" id="L524" title="All 2 branches missed.">                return weasyPrintSessionLimit &gt; 0 ? weasyPrintSessionLimit : 16;</span>
    +            }
    +
    +            public int getInstallAppSessionLimit() {
    +<span class="nc bnc" id="L528" title="All 2 branches missed.">                return installAppSessionLimit &gt; 0 ? installAppSessionLimit : 1;</span>
    +            }
    +
    +            public int getCalibreSessionLimit() {
    +<span class="nc bnc" id="L532" title="All 2 branches missed.">                return calibreSessionLimit &gt; 0 ? calibreSessionLimit : 1;</span>
    +            }
    +        }
    +
    +        @Data
    +        public static class TimeoutMinutes {
    +            private long libreOfficeTimeoutMinutes;
    +            private long pdfToHtmlTimeoutMinutes;
    +            private long pythonOpenCvTimeoutMinutes;
    +            private long weasyPrintTimeoutMinutes;
    +            private long installAppTimeoutMinutes;
    +            private long calibreTimeoutMinutes;
    +            private long tesseractTimeoutMinutes;
    +            private long qpdfTimeoutMinutes;
    +
    +            public long getTesseractTimeoutMinutes() {
    +<span class="nc bnc" id="L548" title="All 2 branches missed.">                return tesseractTimeoutMinutes &gt; 0 ? tesseractTimeoutMinutes : 30;</span>
    +            }
    +
    +            public long getQpdfTimeoutMinutes() {
    +<span class="nc bnc" id="L552" title="All 2 branches missed.">                return qpdfTimeoutMinutes &gt; 0 ? qpdfTimeoutMinutes : 30;</span>
    +            }
    +
    +            public long getLibreOfficeTimeoutMinutes() {
    +<span class="nc bnc" id="L556" title="All 2 branches missed.">                return libreOfficeTimeoutMinutes &gt; 0 ? libreOfficeTimeoutMinutes : 30;</span>
    +            }
    +
    +            public long getPdfToHtmlTimeoutMinutes() {
    +<span class="pc bpc" id="L560" title="1 of 2 branches missed.">                return pdfToHtmlTimeoutMinutes &gt; 0 ? pdfToHtmlTimeoutMinutes : 20;</span>
    +            }
    +
    +            public long getPythonOpenCvTimeoutMinutes() {
    +<span class="nc bnc" id="L564" title="All 2 branches missed.">                return pythonOpenCvTimeoutMinutes &gt; 0 ? pythonOpenCvTimeoutMinutes : 30;</span>
    +            }
    +
    +            public long getWeasyPrintTimeoutMinutes() {
    +<span class="nc bnc" id="L568" title="All 2 branches missed.">                return weasyPrintTimeoutMinutes &gt; 0 ? weasyPrintTimeoutMinutes : 30;</span>
    +            }
    +
    +            public long getInstallAppTimeoutMinutes() {
    +<span class="nc bnc" id="L572" title="All 2 branches missed.">                return installAppTimeoutMinutes &gt; 0 ? installAppTimeoutMinutes : 60;</span>
    +            }
    +
    +            public long getCalibreTimeoutMinutes() {
    +<span class="nc bnc" id="L576" title="All 2 branches missed.">                return calibreTimeoutMinutes &gt; 0 ? calibreTimeoutMinutes : 30;</span>
    +            }
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/AttemptCounter.html b/testResult/html/stirling.software.SPDF.model/AttemptCounter.html
    new file mode 100644
    index 000000000..83677aed8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/AttemptCounter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AttemptCounter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">AttemptCounter</span></div><h1>AttemptCounter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">43 of 43</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a5"><a href="AttemptCounter.java.html#L26" class="el_method">shouldReset(long)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="11" alt="11"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="AttemptCounter.java.html#L13" class="el_method">increment()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="109" height="10" title="10" alt="10"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="AttemptCounter.java.html#L7" class="el_method">AttemptCounter()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="98" height="10" title="9" alt="9"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="AttemptCounter.java.html#L30" class="el_method">reset()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="76" height="10" title="7" alt="7"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="AttemptCounter.java.html#L18" class="el_method">getAttemptCount()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a2"><a href="AttemptCounter.java.html#L22" class="el_method">getLastAttemptTime()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/AttemptCounter.java.html b/testResult/html/stirling.software.SPDF.model/AttemptCounter.java.html
    new file mode 100644
    index 000000000..43d306053
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/AttemptCounter.java.html
    @@ -0,0 +1,34 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AttemptCounter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">AttemptCounter.java</span></div><h1>AttemptCounter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +public class AttemptCounter {
    +    private int attemptCount;
    +    private long lastAttemptTime;
    +
    +<span class="nc" id="L7">    public AttemptCounter() {</span>
    +<span class="nc" id="L8">        this.attemptCount = 0;</span>
    +<span class="nc" id="L9">        this.lastAttemptTime = System.currentTimeMillis();</span>
    +<span class="nc" id="L10">    }</span>
    +
    +    public void increment() {
    +<span class="nc" id="L13">        this.attemptCount++;</span>
    +<span class="nc" id="L14">        this.lastAttemptTime = System.currentTimeMillis();</span>
    +<span class="nc" id="L15">    }</span>
    +
    +    public int getAttemptCount() {
    +<span class="nc" id="L18">        return attemptCount;</span>
    +    }
    +
    +    public long getLastAttemptTime() {
    +<span class="nc" id="L22">        return lastAttemptTime;</span>
    +    }
    +
    +    public boolean shouldReset(long attemptIncrementTime) {
    +<span class="nc bnc" id="L26" title="All 2 branches missed.">        return System.currentTimeMillis() - lastAttemptTime &gt; attemptIncrementTime;</span>
    +    }
    +
    +    public void reset() {
    +<span class="nc" id="L30">        this.attemptCount = 0;</span>
    +<span class="nc" id="L31">        this.lastAttemptTime = System.currentTimeMillis();</span>
    +<span class="nc" id="L32">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/AuthenticationType.html b/testResult/html/stirling.software.SPDF.model/AuthenticationType.html
    new file mode 100644
    index 000000000..3c97ae4d9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/AuthenticationType.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthenticationType</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">AuthenticationType</span></div><h1>AuthenticationType</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">15 of 15</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="AuthenticationType.java.html#L3" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="15" alt="15"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/AuthenticationType.java.html b/testResult/html/stirling.software.SPDF.model/AuthenticationType.java.html
    new file mode 100644
    index 000000000..47a02a708
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/AuthenticationType.java.html
    @@ -0,0 +1,7 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>AuthenticationType.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">AuthenticationType.java</span></div><h1>AuthenticationType.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +<span class="nc" id="L3">public enum AuthenticationType {</span>
    +<span class="nc" id="L4">    WEB,</span>
    +<span class="nc" id="L5">    SSO</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/Authority.html b/testResult/html/stirling.software.SPDF.model/Authority.html
    new file mode 100644
    index 000000000..0a8abd4ce
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/Authority.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Authority</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">Authority</span></div><h1>Authority</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">38 of 38</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">8</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a1"><a href="Authority.java.html#L26" class="el_method">Authority(String, User)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a6"><a href="Authority.java.html#L37" class="el_method">setId(Long)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="Authority.java.html#L45" class="el_method">setAuthority(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a7"><a href="Authority.java.html#L53" class="el_method">setUser(User)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="Authority.java.html#L24" class="el_method">Authority()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="Authority.java.html#L33" class="el_method">getId()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="Authority.java.html#L41" class="el_method">getAuthority()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a4"><a href="Authority.java.html#L49" class="el_method">getUser()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="3" alt="3"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/Authority.java.html b/testResult/html/stirling.software.SPDF.model/Authority.java.html
    new file mode 100644
    index 000000000..abefb53e2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/Authority.java.html
    @@ -0,0 +1,56 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Authority.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">Authority.java</span></div><h1>Authority.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.io.Serializable;
    +
    +import jakarta.persistence.*;
    +
    +@Entity
    +@Table(name = &quot;authorities&quot;)
    +public class Authority implements Serializable {
    +
    +    private static final long serialVersionUID = 1L;
    +
    +    @Id
    +    @GeneratedValue(strategy = GenerationType.IDENTITY)
    +    private Long id;
    +
    +    @Column(name = &quot;authority&quot;)
    +    private String authority;
    +
    +    @ManyToOne
    +    @JoinColumn(name = &quot;user_id&quot;)
    +    private User user;
    +
    +<span class="nc" id="L24">    public Authority() {}</span>
    +
    +<span class="nc" id="L26">    public Authority(String authority, User user) {</span>
    +<span class="nc" id="L27">        this.authority = authority;</span>
    +<span class="nc" id="L28">        this.user = user;</span>
    +<span class="nc" id="L29">        user.getAuthorities().add(this);</span>
    +<span class="nc" id="L30">    }</span>
    +
    +    public Long getId() {
    +<span class="nc" id="L33">        return id;</span>
    +    }
    +
    +    public void setId(Long id) {
    +<span class="nc" id="L37">        this.id = id;</span>
    +<span class="nc" id="L38">    }</span>
    +
    +    public String getAuthority() {
    +<span class="nc" id="L41">        return authority;</span>
    +    }
    +
    +    public void setAuthority(String authority) {
    +<span class="nc" id="L45">        this.authority = authority;</span>
    +<span class="nc" id="L46">    }</span>
    +
    +    public User getUser() {
    +<span class="nc" id="L49">        return user;</span>
    +    }
    +
    +    public void setUser(User user) {
    +<span class="nc" id="L53">        this.user = user;</span>
    +<span class="nc" id="L54">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/InputStreamTemplateResource.html b/testResult/html/stirling.software.SPDF.model/InputStreamTemplateResource.html
    new file mode 100644
    index 000000000..06fe01183
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/InputStreamTemplateResource.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InputStreamTemplateResource</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">InputStreamTemplateResource</span></div><h1>InputStreamTemplateResource</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">28 of 28</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a3"><a href="InputStreamTemplateResource.java.html#L14" class="el_method">InputStreamTemplateResource(InputStream, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a4"><a href="InputStreamTemplateResource.java.html#L21" class="el_method">reader()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="106" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="InputStreamTemplateResource.java.html#L27" class="el_method">relative(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="5" alt="5"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="InputStreamTemplateResource.java.html#L32" class="el_method">getDescription()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="2" alt="2"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="InputStreamTemplateResource.java.html#L37" class="el_method">getBaseName()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="2" alt="2"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="InputStreamTemplateResource.java.html#L43" class="el_method">exists()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="2" alt="2"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/InputStreamTemplateResource.java.html b/testResult/html/stirling.software.SPDF.model/InputStreamTemplateResource.java.html
    new file mode 100644
    index 000000000..90b35da94
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/InputStreamTemplateResource.java.html
    @@ -0,0 +1,46 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InputStreamTemplateResource.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">InputStreamTemplateResource.java</span></div><h1>InputStreamTemplateResource.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.InputStreamReader;
    +import java.io.Reader;
    +
    +import org.thymeleaf.templateresource.ITemplateResource;
    +
    +public class InputStreamTemplateResource implements ITemplateResource {
    +    private InputStream inputStream;
    +    private String characterEncoding;
    +
    +<span class="nc" id="L14">    public InputStreamTemplateResource(InputStream inputStream, String characterEncoding) {</span>
    +<span class="nc" id="L15">        this.inputStream = inputStream;</span>
    +<span class="nc" id="L16">        this.characterEncoding = characterEncoding;</span>
    +<span class="nc" id="L17">    }</span>
    +
    +    @Override
    +    public Reader reader() throws IOException {
    +<span class="nc" id="L21">        return new InputStreamReader(inputStream, characterEncoding);</span>
    +    }
    +
    +    @Override
    +    public ITemplateResource relative(String relativeLocation) {
    +        // Implement logic for relative resources, if needed
    +<span class="nc" id="L27">        throw new UnsupportedOperationException(&quot;Relative resources not supported&quot;);</span>
    +    }
    +
    +    @Override
    +    public String getDescription() {
    +<span class="nc" id="L32">        return &quot;InputStream resource [Stream]&quot;;</span>
    +    }
    +
    +    @Override
    +    public String getBaseName() {
    +<span class="nc" id="L37">        return &quot;streamResource&quot;;</span>
    +    }
    +
    +    @Override
    +    public boolean exists() {
    +        // TODO Auto-generated method stub
    +<span class="nc" id="L43">        return false;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/PersistentLogin.html b/testResult/html/stirling.software.SPDF.model/PersistentLogin.html
    new file mode 100644
    index 000000000..671d8ad37
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/PersistentLogin.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PersistentLogin</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">PersistentLogin</span></div><h1>PersistentLogin</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">31 of 31</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a6"><a href="PersistentLogin.java.html#L32" class="el_method">setSeries(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a8"><a href="PersistentLogin.java.html#L40" class="el_method">setUsername(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a7"><a href="PersistentLogin.java.html#L48" class="el_method">setToken(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a5"><a href="PersistentLogin.java.html#L56" class="el_method">setLastUsed(Date)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="PersistentLogin.java.html#L12" class="el_method">PersistentLogin()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="PersistentLogin.java.html#L28" class="el_method">getSeries()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a3"><a href="PersistentLogin.java.html#L36" class="el_method">getUsername()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="PersistentLogin.java.html#L44" class="el_method">getToken()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a0"><a href="PersistentLogin.java.html#L52" class="el_method">getLastUsed()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/PersistentLogin.java.html b/testResult/html/stirling.software.SPDF.model/PersistentLogin.java.html
    new file mode 100644
    index 000000000..8b5111348
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/PersistentLogin.java.html
    @@ -0,0 +1,61 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PersistentLogin.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">PersistentLogin.java</span></div><h1>PersistentLogin.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.util.Date;
    +
    +import jakarta.persistence.Column;
    +import jakarta.persistence.Entity;
    +import jakarta.persistence.Id;
    +import jakarta.persistence.Table;
    +
    +@Entity
    +@Table(name = &quot;persistent_logins&quot;)
    +<span class="nc" id="L12">public class PersistentLogin {</span>
    +
    +    @Id
    +    @Column(name = &quot;series&quot;)
    +    private String series;
    +
    +    @Column(name = &quot;username&quot;, length = 64, nullable = false)
    +    private String username;
    +
    +    @Column(name = &quot;token&quot;, length = 64, nullable = false)
    +    private String token;
    +
    +    @Column(name = &quot;last_used&quot;, nullable = false)
    +    private Date lastUsed;
    +
    +    public String getSeries() {
    +<span class="nc" id="L28">        return series;</span>
    +    }
    +
    +    public void setSeries(String series) {
    +<span class="nc" id="L32">        this.series = series;</span>
    +<span class="nc" id="L33">    }</span>
    +
    +    public String getUsername() {
    +<span class="nc" id="L36">        return username;</span>
    +    }
    +
    +    public void setUsername(String username) {
    +<span class="nc" id="L40">        this.username = username;</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    public String getToken() {
    +<span class="nc" id="L44">        return token;</span>
    +    }
    +
    +    public void setToken(String token) {
    +<span class="nc" id="L48">        this.token = token;</span>
    +<span class="nc" id="L49">    }</span>
    +
    +    public Date getLastUsed() {
    +<span class="nc" id="L52">        return lastUsed;</span>
    +    }
    +
    +    public void setLastUsed(Date lastUsed) {
    +<span class="nc" id="L56">        this.lastUsed = lastUsed;</span>
    +<span class="nc" id="L57">    }</span>
    +
    +    // Getters, setters, etc.
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/PipelineConfig.html b/testResult/html/stirling.software.SPDF.model/PipelineConfig.html
    new file mode 100644
    index 000000000..afe1849c7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/PipelineConfig.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineConfig</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">PipelineConfig</span></div><h1>PipelineConfig</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">31 of 31</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a5"><a href="PipelineConfig.java.html#L23" class="el_method">setName(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a6"><a href="PipelineConfig.java.html#L31" class="el_method">setOperations(List)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a7"><a href="PipelineConfig.java.html#L39" class="el_method">setOutputDir(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a8"><a href="PipelineConfig.java.html#L47" class="el_method">setOutputPattern(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="PipelineConfig.java.html#L7" class="el_method">PipelineConfig()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="PipelineConfig.java.html#L19" class="el_method">getName()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a1"><a href="PipelineConfig.java.html#L27" class="el_method">getOperations()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="PipelineConfig.java.html#L35" class="el_method">getOutputDir()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a3"><a href="PipelineConfig.java.html#L43" class="el_method">getOutputPattern()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/PipelineConfig.java.html b/testResult/html/stirling.software.SPDF.model/PipelineConfig.java.html
    new file mode 100644
    index 000000000..df34cc66a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/PipelineConfig.java.html
    @@ -0,0 +1,50 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineConfig.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">PipelineConfig.java</span></div><h1>PipelineConfig.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.util.List;
    +
    +import com.fasterxml.jackson.annotation.JsonProperty;
    +
    +<span class="nc" id="L7">public class PipelineConfig {</span>
    +    private String name;
    +
    +    @JsonProperty(&quot;pipeline&quot;)
    +    private List&lt;PipelineOperation&gt; operations;
    +
    +    private String outputDir;
    +
    +    @JsonProperty(&quot;outputFileName&quot;)
    +    private String outputPattern;
    +
    +    public String getName() {
    +<span class="nc" id="L19">        return name;</span>
    +    }
    +
    +    public void setName(String name) {
    +<span class="nc" id="L23">        this.name = name;</span>
    +<span class="nc" id="L24">    }</span>
    +
    +    public List&lt;PipelineOperation&gt; getOperations() {
    +<span class="nc" id="L27">        return operations;</span>
    +    }
    +
    +    public void setOperations(List&lt;PipelineOperation&gt; operations) {
    +<span class="nc" id="L31">        this.operations = operations;</span>
    +<span class="nc" id="L32">    }</span>
    +
    +    public String getOutputDir() {
    +<span class="nc" id="L35">        return outputDir;</span>
    +    }
    +
    +    public void setOutputDir(String outputDir) {
    +<span class="nc" id="L39">        this.outputDir = outputDir;</span>
    +<span class="nc" id="L40">    }</span>
    +
    +    public String getOutputPattern() {
    +<span class="nc" id="L43">        return outputPattern;</span>
    +    }
    +
    +    public void setOutputPattern(String outputPattern) {
    +<span class="nc" id="L47">        this.outputPattern = outputPattern;</span>
    +<span class="nc" id="L48">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/PipelineOperation.html b/testResult/html/stirling.software.SPDF.model/PipelineOperation.html
    new file mode 100644
    index 000000000..c6e03ea2b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/PipelineOperation.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineOperation</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">PipelineOperation</span></div><h1>PipelineOperation</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a5"><a href="PipelineOperation.java.html#L27" class="el_method">toString()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="7" alt="7"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="PipelineOperation.java.html#L14" class="el_method">setOperation(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="PipelineOperation.java.html#L22" class="el_method">setParameters(Map)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="PipelineOperation.java.html#L5" class="el_method">PipelineOperation()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="PipelineOperation.java.html#L10" class="el_method">getOperation()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="PipelineOperation.java.html#L18" class="el_method">getParameters()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/PipelineOperation.java.html b/testResult/html/stirling.software.SPDF.model/PipelineOperation.java.html
    new file mode 100644
    index 000000000..93af42616
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/PipelineOperation.java.html
    @@ -0,0 +1,30 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PipelineOperation.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">PipelineOperation.java</span></div><h1>PipelineOperation.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.util.Map;
    +
    +<span class="nc" id="L5">public class PipelineOperation {</span>
    +    private String operation;
    +    private Map&lt;String, Object&gt; parameters;
    +
    +    public String getOperation() {
    +<span class="nc" id="L10">        return operation;</span>
    +    }
    +
    +    public void setOperation(String operation) {
    +<span class="nc" id="L14">        this.operation = operation;</span>
    +<span class="nc" id="L15">    }</span>
    +
    +    public Map&lt;String, Object&gt; getParameters() {
    +<span class="nc" id="L18">        return parameters;</span>
    +    }
    +
    +    public void setParameters(Map&lt;String, Object&gt; parameters) {
    +<span class="nc" id="L22">        this.parameters = parameters;</span>
    +<span class="nc" id="L23">    }</span>
    +
    +    @Override
    +    public String toString() {
    +<span class="nc" id="L27">        return &quot;PipelineOperation [operation=&quot; + operation + &quot;, parameters=&quot; + parameters + &quot;]&quot;;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/Role.html b/testResult/html/stirling.software.SPDF.model/Role.html
    new file mode 100644
    index 000000000..ccf8e614e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/Role.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Role</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">Role</span></div><h1>Role</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">166 of 166</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">28</td><td class="ctr2">28</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a8"><a href="Role.java.html#L6" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="73" alt="73"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="Role.java.html#L53" class="el_method">getAllRoleDetails()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="47" height="10" title="29" alt="29"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="Role.java.html#L61" class="el_method">fromString(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="47" height="10" title="29" alt="29"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a7"><a href="Role.java.html#L36" class="el_method">Role(String, int, String, int, int, String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="17" alt="17"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="Role.java.html#L45" class="el_method">getRoleNameByRoleId(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="Role.java.html#L70" class="el_method">getRoleId()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="Role.java.html#L74" class="el_method">getApiCallsPerDay()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a6"><a href="Role.java.html#L78" class="el_method">getWebCallsPerDay()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a4"><a href="Role.java.html#L82" class="el_method">getRoleName()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/Role.java.html b/testResult/html/stirling.software.SPDF.model/Role.java.html
    new file mode 100644
    index 000000000..4d6c3fbf7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/Role.java.html
    @@ -0,0 +1,85 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Role.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">Role.java</span></div><h1>Role.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.util.LinkedHashMap;
    +import java.util.Map;
    +
    +<span class="nc" id="L6">public enum Role {</span>
    +
    +    // Unlimited access
    +<span class="nc" id="L9">    ADMIN(&quot;ROLE_ADMIN&quot;, Integer.MAX_VALUE, Integer.MAX_VALUE, &quot;adminUserSettings.admin&quot;),</span>
    +
    +    // Unlimited access
    +<span class="nc" id="L12">    USER(&quot;ROLE_USER&quot;, Integer.MAX_VALUE, Integer.MAX_VALUE, &quot;adminUserSettings.user&quot;),</span>
    +
    +    // 40 API calls Per Day, 40 web calls
    +<span class="nc" id="L15">    LIMITED_API_USER(&quot;ROLE_LIMITED_API_USER&quot;, 40, 40, &quot;adminUserSettings.apiUser&quot;),</span>
    +
    +    // 20 API calls Per Day, 20 web calls
    +<span class="nc" id="L18">    EXTRA_LIMITED_API_USER(&quot;ROLE_EXTRA_LIMITED_API_USER&quot;, 20, 20, &quot;adminUserSettings.extraApiUser&quot;),</span>
    +
    +    // 0 API calls per day and 20 web calls
    +<span class="nc" id="L21">    WEB_ONLY_USER(&quot;ROLE_WEB_ONLY_USER&quot;, 0, 20, &quot;adminUserSettings.webOnlyUser&quot;),</span>
    +
    +<span class="nc" id="L23">    INTERNAL_API_USER(</span>
    +            &quot;STIRLING-PDF-BACKEND-API-USER&quot;,
    +            Integer.MAX_VALUE,
    +            Integer.MAX_VALUE,
    +            &quot;adminUserSettings.internalApiUser&quot;),
    +
    +<span class="nc" id="L29">    DEMO_USER(&quot;ROLE_DEMO_USER&quot;, 100, 100, &quot;adminUserSettings.demoUser&quot;);</span>
    +
    +    private final String roleId;
    +    private final int apiCallsPerDay;
    +    private final int webCallsPerDay;
    +    private final String roleName;
    +
    +<span class="nc" id="L36">    Role(String roleId, int apiCallsPerDay, int webCallsPerDay, String roleName) {</span>
    +<span class="nc" id="L37">        this.roleId = roleId;</span>
    +<span class="nc" id="L38">        this.apiCallsPerDay = apiCallsPerDay;</span>
    +<span class="nc" id="L39">        this.webCallsPerDay = webCallsPerDay;</span>
    +<span class="nc" id="L40">        this.roleName = roleName;</span>
    +<span class="nc" id="L41">    }</span>
    +
    +    public static String getRoleNameByRoleId(String roleId) {
    +        // Using the fromString method to get the Role enum based on the roleId
    +<span class="nc" id="L45">        Role role = fromString(roleId);</span>
    +        // Return the roleName of the found Role enum
    +<span class="nc" id="L47">        return role.getRoleName();</span>
    +    }
    +
    +    // Method to retrieve all role IDs and role names
    +    public static Map&lt;String, String&gt; getAllRoleDetails() {
    +        // Using LinkedHashMap to preserve order
    +<span class="nc" id="L53">        Map&lt;String, String&gt; roleDetails = new LinkedHashMap&lt;&gt;();</span>
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">        for (Role role : Role.values()) {</span>
    +<span class="nc" id="L55">            roleDetails.put(role.getRoleId(), role.getRoleName());</span>
    +        }
    +<span class="nc" id="L57">        return roleDetails;</span>
    +    }
    +
    +    public static Role fromString(String roleId) {
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        for (Role role : Role.values()) {</span>
    +<span class="nc bnc" id="L62" title="All 2 branches missed.">            if (role.getRoleId().equalsIgnoreCase(roleId)) {</span>
    +<span class="nc" id="L63">                return role;</span>
    +            }
    +        }
    +<span class="nc" id="L66">        throw new IllegalArgumentException(&quot;No Role defined for id: &quot; + roleId);</span>
    +    }
    +
    +    public String getRoleId() {
    +<span class="nc" id="L70">        return roleId;</span>
    +    }
    +
    +    public int getApiCallsPerDay() {
    +<span class="nc" id="L74">        return apiCallsPerDay;</span>
    +    }
    +
    +    public int getWebCallsPerDay() {
    +<span class="nc" id="L78">        return webCallsPerDay;</span>
    +    }
    +
    +    public String getRoleName() {
    +<span class="nc" id="L82">        return roleName;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/SortTypes.html b/testResult/html/stirling.software.SPDF.model/SortTypes.html
    new file mode 100644
    index 000000000..64182820c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/SortTypes.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SortTypes</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">SortTypes</span></div><h1>SortTypes</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">69 of 69</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="SortTypes.java.html#L3" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="69" alt="69"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/SortTypes.java.html b/testResult/html/stirling.software.SPDF.model/SortTypes.java.html
    new file mode 100644
    index 000000000..54bc8a939
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/SortTypes.java.html
    @@ -0,0 +1,16 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SortTypes.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">SortTypes.java</span></div><h1>SortTypes.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +<span class="nc" id="L3">public enum SortTypes {</span>
    +<span class="nc" id="L4">    CUSTOM,</span>
    +<span class="nc" id="L5">    REVERSE_ORDER,</span>
    +<span class="nc" id="L6">    DUPLEX_SORT,</span>
    +<span class="nc" id="L7">    BOOKLET_SORT,</span>
    +<span class="nc" id="L8">    SIDE_STITCH_BOOKLET_SORT,</span>
    +<span class="nc" id="L9">    ODD_EVEN_SPLIT,</span>
    +<span class="nc" id="L10">    ODD_EVEN_MERGE,</span>
    +<span class="nc" id="L11">    REMOVE_FIRST,</span>
    +<span class="nc" id="L12">    REMOVE_LAST,</span>
    +<span class="nc" id="L13">    REMOVE_FIRST_AND_LAST,</span>
    +<span class="nc" id="L14">    DUPLICATE</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/User.html b/testResult/html/stirling.software.SPDF.model/User.html
    new file mode 100644
    index 000000000..afa32de78
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/User.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>User</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">User</span></div><h1>User</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">128 of 128</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">28</td><td class="ctr2">28</td><td class="ctr1">41</td><td class="ctr2">41</td><td class="ctr1">24</td><td class="ctr2">24</td></tr></tfoot><tbody><tr><td id="a23"><a href="User.java.html#L14" class="el_method">User()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="17" alt="17"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a13"><a href="User.java.html#L59" class="el_method">isFirstLogin()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="77" height="10" title="11" alt="11"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h13">1</td><td class="ctr2" id="i13">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a11"><a href="User.java.html#L145" class="el_method">hasPassword()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="77" height="10" title="11" alt="11"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h14">1</td><td class="ctr2" id="i14">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a8"><a href="User.java.html#L139" class="el_method">getRolesAsString()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="10" alt="10"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a15"><a href="User.java.html#L119" class="el_method">setAuthenticationType(AuthenticationType)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="User.java.html#L131" class="el_method">addAuthorities(Set)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="6" alt="6"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a1"><a href="User.java.html#L135" class="el_method">addAuthority(Authority)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="6" alt="6"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a18"><a href="User.java.html#L63" class="el_method">setFirstLogin(boolean)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="5" alt="5"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h5">2</td><td class="ctr2" id="i5">2</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a7"><a href="User.java.html#L55" class="el_method">getRoleName()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h15">1</td><td class="ctr2" id="i15">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a19"><a href="User.java.html#L71" class="el_method">setId(Long)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h6">2</td><td class="ctr2" id="i6">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a14"><a href="User.java.html#L79" class="el_method">setApiKey(String)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h7">2</td><td class="ctr2" id="i7">2</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a21"><a href="User.java.html#L87" class="el_method">setSettings(Map)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a22"><a href="User.java.html#L95" class="el_method">setUsername(String)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h9">2</td><td class="ctr2" id="i9">2</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a20"><a href="User.java.html#L103" class="el_method">setPassword(String)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h10">2</td><td class="ctr2" id="i10">2</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a17"><a href="User.java.html#L111" class="el_method">setEnabled(boolean)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h11">2</td><td class="ctr2" id="i11">2</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a16"><a href="User.java.html#L127" class="el_method">setAuthorities(Set)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="4" alt="4"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h12">2</td><td class="ctr2" id="i12">2</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a5"><a href="User.java.html#L67" class="el_method">getId()</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h16">1</td><td class="ctr2" id="i16">1</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a2"><a href="User.java.html#L75" class="el_method">getApiKey()</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h17">1</td><td class="ctr2" id="i17">1</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a9"><a href="User.java.html#L83" class="el_method">getSettings()</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h18">1</td><td class="ctr2" id="i18">1</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a10"><a href="User.java.html#L91" class="el_method">getUsername()</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h19">1</td><td class="ctr2" id="i19">1</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a6"><a href="User.java.html#L99" class="el_method">getPassword()</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">1</td><td class="ctr2" id="i20">1</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a12"><a href="User.java.html#L107" class="el_method">isEnabled()</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">1</td><td class="ctr2" id="i21">1</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a3"><a href="User.java.html#L115" class="el_method">getAuthenticationType()</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h22">1</td><td class="ctr2" id="i22">1</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a4"><a href="User.java.html#L123" class="el_method">getAuthorities()</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="3" alt="3"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">1</td><td class="ctr2" id="i23">1</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/User.java.html b/testResult/html/stirling.software.SPDF.model/User.java.html
    new file mode 100644
    index 000000000..8eaf439dd
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/User.java.html
    @@ -0,0 +1,148 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>User.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">User.java</span></div><h1>User.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import java.io.Serializable;
    +import java.util.HashMap;
    +import java.util.HashSet;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.stream.Collectors;
    +
    +import jakarta.persistence.*;
    +
    +@Entity
    +@Table(name = &quot;users&quot;)
    +<span class="nc" id="L14">public class User implements Serializable {</span>
    +
    +    private static final long serialVersionUID = 1L;
    +
    +    @Id
    +    @GeneratedValue(strategy = GenerationType.IDENTITY)
    +    @Column(name = &quot;user_id&quot;)
    +    private Long id;
    +
    +    @Column(name = &quot;username&quot;, unique = true)
    +    private String username;
    +
    +    @Column(name = &quot;password&quot;)
    +    private String password;
    +
    +    @Column(name = &quot;apiKey&quot;)
    +    private String apiKey;
    +
    +    @Column(name = &quot;enabled&quot;)
    +    private boolean enabled;
    +
    +<span class="nc" id="L35">    @Column(name = &quot;isFirstLogin&quot;)</span>
    +<span class="nc" id="L36">    private Boolean isFirstLogin = false;</span>
    +
    +    @Column(name = &quot;roleName&quot;)
    +    private String roleName;
    +
    +    @Column(name = &quot;authenticationtype&quot;)
    +    private String authenticationType;
    +
    +<span class="nc" id="L44">    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = &quot;user&quot;)</span>
    +    private Set&lt;Authority&gt; authorities = new HashSet&lt;&gt;();
    +
    +<span class="nc" id="L47">    @ElementCollection</span>
    +    @MapKeyColumn(name = &quot;setting_key&quot;)
    +    @Lob
    +    @Column(name = &quot;setting_value&quot;, columnDefinition = &quot;text&quot;)
    +    @CollectionTable(name = &quot;user_settings&quot;, joinColumns = @JoinColumn(name = &quot;user_id&quot;))
    +    private Map&lt;String, String&gt; settings = new HashMap&lt;&gt;(); // Key-value pairs of settings.
    +
    +    public String getRoleName() {
    +<span class="nc" id="L55">        return Role.getRoleNameByRoleId(getRolesAsString());</span>
    +    }
    +
    +    public boolean isFirstLogin() {
    +<span class="nc bnc" id="L59" title="All 4 branches missed.">        return isFirstLogin != null &amp;&amp; isFirstLogin;</span>
    +    }
    +
    +    public void setFirstLogin(boolean isFirstLogin) {
    +<span class="nc" id="L63">        this.isFirstLogin = isFirstLogin;</span>
    +<span class="nc" id="L64">    }</span>
    +
    +    public Long getId() {
    +<span class="nc" id="L67">        return id;</span>
    +    }
    +
    +    public void setId(Long id) {
    +<span class="nc" id="L71">        this.id = id;</span>
    +<span class="nc" id="L72">    }</span>
    +
    +    public String getApiKey() {
    +<span class="nc" id="L75">        return apiKey;</span>
    +    }
    +
    +    public void setApiKey(String apiKey) {
    +<span class="nc" id="L79">        this.apiKey = apiKey;</span>
    +<span class="nc" id="L80">    }</span>
    +
    +    public Map&lt;String, String&gt; getSettings() {
    +<span class="nc" id="L83">        return settings;</span>
    +    }
    +
    +    public void setSettings(Map&lt;String, String&gt; settings) {
    +<span class="nc" id="L87">        this.settings = settings;</span>
    +<span class="nc" id="L88">    }</span>
    +
    +    public String getUsername() {
    +<span class="nc" id="L91">        return username;</span>
    +    }
    +
    +    public void setUsername(String username) {
    +<span class="nc" id="L95">        this.username = username;</span>
    +<span class="nc" id="L96">    }</span>
    +
    +    public String getPassword() {
    +<span class="nc" id="L99">        return password;</span>
    +    }
    +
    +    public void setPassword(String password) {
    +<span class="nc" id="L103">        this.password = password;</span>
    +<span class="nc" id="L104">    }</span>
    +
    +    public boolean isEnabled() {
    +<span class="nc" id="L107">        return enabled;</span>
    +    }
    +
    +    public void setEnabled(boolean enabled) {
    +<span class="nc" id="L111">        this.enabled = enabled;</span>
    +<span class="nc" id="L112">    }</span>
    +
    +    public String getAuthenticationType() {
    +<span class="nc" id="L115">        return authenticationType;</span>
    +    }
    +
    +    public void setAuthenticationType(AuthenticationType authenticationType) {
    +<span class="nc" id="L119">        this.authenticationType = authenticationType.toString().toLowerCase();</span>
    +<span class="nc" id="L120">    }</span>
    +
    +    public Set&lt;Authority&gt; getAuthorities() {
    +<span class="nc" id="L123">        return authorities;</span>
    +    }
    +
    +    public void setAuthorities(Set&lt;Authority&gt; authorities) {
    +<span class="nc" id="L127">        this.authorities = authorities;</span>
    +<span class="nc" id="L128">    }</span>
    +
    +    public void addAuthorities(Set&lt;Authority&gt; authorities) {
    +<span class="nc" id="L131">        this.authorities.addAll(authorities);</span>
    +<span class="nc" id="L132">    }</span>
    +
    +    public void addAuthority(Authority authorities) {
    +<span class="nc" id="L135">        this.authorities.add(authorities);</span>
    +<span class="nc" id="L136">    }</span>
    +
    +    public String getRolesAsString() {
    +<span class="nc" id="L139">        return this.authorities.stream()</span>
    +<span class="nc" id="L140">                .map(Authority::getAuthority)</span>
    +<span class="nc" id="L141">                .collect(Collectors.joining(&quot;, &quot;));</span>
    +    }
    +
    +    public boolean hasPassword() {
    +<span class="nc bnc" id="L145" title="All 4 branches missed.">        return this.password != null &amp;&amp; !this.password.isEmpty();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/UsernameAttribute.html b/testResult/html/stirling.software.SPDF.model/UsernameAttribute.html
    new file mode 100644
    index 000000000..8be964f7f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/UsernameAttribute.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UsernameAttribute</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_class">UsernameAttribute</span></div><h1>UsernameAttribute</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">102 of 102</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">17</td><td class="ctr2">17</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="UsernameAttribute.java.html#L5" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="94" alt="94"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">14</td><td class="ctr2" id="i0">14</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="UsernameAttribute.java.html#L23" class="el_method">UsernameAttribute(String, int, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/UsernameAttribute.java.html b/testResult/html/stirling.software.SPDF.model/UsernameAttribute.java.html
    new file mode 100644
    index 000000000..8c4e69437
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/UsernameAttribute.java.html
    @@ -0,0 +1,27 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UsernameAttribute.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.model</a> &gt; <span class="el_source">UsernameAttribute.java</span></div><h1>UsernameAttribute.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.model;
    +
    +import lombok.Getter;
    +
    +<span class="nc" id="L5">@Getter</span>
    +public enum UsernameAttribute {
    +<span class="nc" id="L7">    MAIL(&quot;mail&quot;),</span>
    +<span class="nc" id="L8">    EMAIL(&quot;email&quot;),</span>
    +<span class="nc" id="L9">    LOGIN(&quot;login&quot;),</span>
    +<span class="nc" id="L10">    PROFILE(&quot;profile&quot;),</span>
    +<span class="nc" id="L11">    NAME(&quot;name&quot;),</span>
    +<span class="nc" id="L12">    UID(&quot;uid&quot;),</span>
    +<span class="nc" id="L13">    USERNAME(&quot;username&quot;),</span>
    +<span class="nc" id="L14">    NICKNAME(&quot;nickname&quot;),</span>
    +<span class="nc" id="L15">    GIVEN_NAME(&quot;given_name&quot;),</span>
    +<span class="nc" id="L16">    MIDDLE_NAME(&quot;middle_name&quot;),</span>
    +<span class="nc" id="L17">    FAMILY_NAME(&quot;family_name&quot;),</span>
    +<span class="nc" id="L18">    PREFERRED_NAME(&quot;preferred_name&quot;),</span>
    +<span class="nc" id="L19">    PREFERRED_USERNAME(&quot;preferred_username&quot;);</span>
    +
    +    private final String name;
    +
    +<span class="nc" id="L23">    UsernameAttribute(final String name) {</span>
    +<span class="nc" id="L24">        this.name = name;</span>
    +<span class="nc" id="L25">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/index.html b/testResult/html/stirling.software.SPDF.model/index.html
    new file mode 100644
    index 000000000..9c22c4674
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model</span></div><h1>stirling.software.SPDF.model</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,472 of 1,506</td><td class="ctr2">2%</td><td class="bar">154 of 156</td><td class="ctr2">1%</td><td class="ctr1">216</td><td class="ctr2">220</td><td class="ctr1">318</td><td class="ctr2">326</td><td class="ctr1">137</td><td class="ctr2">141</td><td class="ctr1">23</td><td class="ctr2">27</td></tr></tfoot><tbody><tr><td id="a23"><a href="Role.html" class="el_class">Role</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="166" alt="166"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f5">12</td><td class="ctr2" id="g5">12</td><td class="ctr1" id="h1">28</td><td class="ctr2" id="i1">28</td><td class="ctr1" id="j1">9</td><td class="ctr2" id="k1">9</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a25"><a href="User.html" class="el_class">User</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="92" height="10" title="128" alt="128"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f0">28</td><td class="ctr2" id="g0">28</td><td class="ctr1" id="h0">41</td><td class="ctr2" id="i0">41</td><td class="ctr1" id="j0">24</td><td class="ctr2" id="k0">24</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a13"><a href="ApplicationProperties$Security$SAML2.html" class="el_class">ApplicationProperties.Security.SAML2</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="81" height="10" title="113" alt="113"/><img src="../jacoco-resources/greenbar.gif" width="13" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">13%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="12" alt="12"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f6">10</td><td class="ctr2" id="g6">11</td><td class="ctr1" id="h2">21</td><td class="ctr2" id="i2">26</td><td class="ctr1" id="j12">4</td><td class="ctr2" id="k11">5</td><td class="ctr1" id="l23">0</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a26"><a href="UsernameAttribute.html" class="el_class">UsernameAttribute</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="73" height="10" title="102" alt="102"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f24">2</td><td class="ctr2" id="g24">2</td><td class="ctr1" id="h4">17</td><td class="ctr2" id="i4">17</td><td class="ctr1" id="j19">2</td><td class="ctr2" id="k19">2</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a8"><a href="ApplicationProperties$ProcessExecutor$TimeoutMinutes.html" class="el_class">ApplicationProperties.ProcessExecutor.TimeoutMinutes</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="52" height="10" title="73" alt="73"/><img src="../jacoco-resources/greenbar.gif" width="5" height="10" title="7" alt="7"/></td><td class="ctr2" id="c1">8%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="15" alt="15"/><img src="../jacoco-resources/greenbar.gif" width="6" height="10" title="1" alt="1"/></td><td class="ctr2" id="e0">6%</td><td class="ctr1" id="f1">15</td><td class="ctr2" id="g1">16</td><td class="ctr1" id="h18">7</td><td class="ctr2" id="i17">8</td><td class="ctr1" id="j5">7</td><td class="ctr2" id="k4">8</td><td class="ctr1" id="l24">0</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a24"><a href="SortTypes.html" class="el_class">SortTypes</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="49" height="10" title="69" alt="69"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g25">1</td><td class="ctr1" id="h11">12</td><td class="ctr2" id="i11">12</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k23">1</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a9"><a href="ApplicationProperties$Security.html" class="el_class">ApplicationProperties.Security</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="67" alt="67"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f3">14</td><td class="ctr2" id="g3">14</td><td class="ctr1" id="h14">9</td><td class="ctr2" id="i14">9</td><td class="ctr1" id="j13">4</td><td class="ctr2" id="k13">4</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a0"><a href="ApiEndpoint.html" class="el_class">ApiEndpoint</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="47" height="10" title="66" alt="66"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="4" alt="4"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f12">7</td><td class="ctr2" id="g12">7</td><td class="ctr1" id="h5">17</td><td class="ctr2" id="i5">17</td><td class="ctr1" id="j11">5</td><td class="ctr2" id="k12">5</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a7"><a href="ApplicationProperties$ProcessExecutor$SessionLimit.html" class="el_class">ApplicationProperties.ProcessExecutor.SessionLimit</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="59" alt="59"/><img src="../jacoco-resources/greenbar.gif" width="3" height="10" title="5" alt="5"/></td><td class="ctr2" id="c2">7%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="15" alt="15"/><img src="../jacoco-resources/greenbar.gif" width="6" height="10" title="1" alt="1"/></td><td class="ctr2" id="e1">6%</td><td class="ctr1" id="f2">15</td><td class="ctr2" id="g2">16</td><td class="ctr1" id="h19">7</td><td class="ctr2" id="i18">8</td><td class="ctr1" id="j6">7</td><td class="ctr2" id="k5">8</td><td class="ctr1" id="l25">0</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a11"><a href="ApplicationProperties$Security$OAUTH2.html" class="el_class">ApplicationProperties.Security.OAUTH2</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="57" alt="57"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="108" height="10" title="18" alt="18"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f4">13</td><td class="ctr2" id="g4">13</td><td class="ctr1" id="h12">11</td><td class="ctr2" id="i12">11</td><td class="ctr1" id="j14">4</td><td class="ctr2" id="k14">4</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a2"><a href="ApplicationProperties.html" class="el_class">ApplicationProperties</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="56" alt="56"/><img src="../jacoco-resources/greenbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">6%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f20">3</td><td class="ctr2" id="g19">4</td><td class="ctr1" id="h7">14</td><td class="ctr2" id="i6">15</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k20">2</td><td class="ctr1" id="l26">0</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a3"><a href="ApplicationProperties$Driver.html" class="el_class">ApplicationProperties.Driver</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="49" alt="49"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f21">3</td><td class="ctr2" id="g21">3</td><td class="ctr1" id="h13">10</td><td class="ctr2" id="i13">10</td><td class="ctr1" id="j15">3</td><td class="ctr2" id="k15">3</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a1"><a href="ApiKeyAuthenticationToken.html" class="el_class">ApiKeyAuthenticationToken</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="49" alt="49"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f13">7</td><td class="ctr2" id="g13">7</td><td class="ctr1" id="h3">19</td><td class="ctr2" id="i3">19</td><td class="ctr1" id="j7">6</td><td class="ctr2" id="k7">6</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a16"><a href="AttemptCounter.html" class="el_class">AttemptCounter</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="43" alt="43"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f14">7</td><td class="ctr2" id="g14">7</td><td class="ctr1" id="h8">13</td><td class="ctr2" id="i8">13</td><td class="ctr1" id="j8">6</td><td class="ctr2" id="k8">6</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a10"><a href="ApplicationProperties$Security$LoginMethods.html" class="el_class">ApplicationProperties.Security.LoginMethods</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="42" alt="42"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f22">3</td><td class="ctr2" id="g22">3</td><td class="ctr1" id="h15">9</td><td class="ctr2" id="i15">9</td><td class="ctr1" id="j16">3</td><td class="ctr2" id="k16">3</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a15"><a href="ApplicationProperties$Ui.html" class="el_class">ApplicationProperties.Ui</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="39" alt="39"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="12" alt="12"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f7">9</td><td class="ctr2" id="g7">9</td><td class="ctr1" id="h20">7</td><td class="ctr2" id="i20">7</td><td class="ctr1" id="j17">3</td><td class="ctr2" id="k17">3</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m15">1</td></tr><tr><td id="a6"><a href="ApplicationProperties$Premium$ProFeatures$GoogleDrive.html" class="el_class">ApplicationProperties.Premium.ProFeatures.GoogleDrive</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="39" alt="39"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="12" alt="12"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f8">9</td><td class="ctr2" id="g8">9</td><td class="ctr1" id="h23">3</td><td class="ctr2" id="i23">3</td><td class="ctr1" id="j18">3</td><td class="ctr2" id="k18">3</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m16">1</td></tr><tr><td id="a18"><a href="Authority.html" class="el_class">Authority</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="38" alt="38"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f11">8</td><td class="ctr2" id="g11">8</td><td class="ctr1" id="h6">15</td><td class="ctr2" id="i7">15</td><td class="ctr1" id="j4">8</td><td class="ctr2" id="k6">8</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m17">1</td></tr><tr><td id="a21"><a href="PipelineConfig.html" class="el_class">PipelineConfig</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="31" alt="31"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f9">9</td><td class="ctr2" id="g9">9</td><td class="ctr1" id="h9">13</td><td class="ctr2" id="i9">13</td><td class="ctr1" id="j2">9</td><td class="ctr2" id="k2">9</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m18">1</td></tr><tr><td id="a20"><a href="PersistentLogin.html" class="el_class">PersistentLogin</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="22" height="10" title="31" alt="31"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f10">9</td><td class="ctr2" id="g10">9</td><td class="ctr1" id="h10">13</td><td class="ctr2" id="i10">13</td><td class="ctr1" id="j3">9</td><td class="ctr2" id="k3">9</td><td class="ctr1" id="l15">1</td><td class="ctr2" id="m19">1</td></tr><tr><td id="a19"><a href="InputStreamTemplateResource.html" class="el_class">InputStreamTemplateResource</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="28" alt="28"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f15">6</td><td class="ctr2" id="g15">6</td><td class="ctr1" id="h16">9</td><td class="ctr2" id="i16">9</td><td class="ctr1" id="j9">6</td><td class="ctr2" id="k9">6</td><td class="ctr1" id="l16">1</td><td class="ctr2" id="m20">1</td></tr><tr><td id="a4"><a href="ApplicationProperties$EnterpriseEdition$CustomMetadata.html" class="el_class">ApplicationProperties.EnterpriseEdition.CustomMetadata</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="26" alt="26"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="8" alt="8"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f16">6</td><td class="ctr2" id="g16">6</td><td class="ctr1" id="h25">2</td><td class="ctr2" id="i25">2</td><td class="ctr1" id="j20">2</td><td class="ctr2" id="k21">2</td><td class="ctr1" id="l17">1</td><td class="ctr2" id="m21">1</td></tr><tr><td id="a5"><a href="ApplicationProperties$Premium$ProFeatures$CustomMetadata.html" class="el_class">ApplicationProperties.Premium.ProFeatures.CustomMetadata</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="26" alt="26"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="8" alt="8"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f17">6</td><td class="ctr2" id="g17">6</td><td class="ctr1" id="h22">4</td><td class="ctr2" id="i22">4</td><td class="ctr1" id="j21">2</td><td class="ctr2" id="k22">2</td><td class="ctr1" id="l18">1</td><td class="ctr2" id="m22">1</td></tr><tr><td id="a12"><a href="ApplicationProperties$Security$OAUTH2$Client.html" class="el_class">ApplicationProperties.Security.OAUTH2.Client</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="25" alt="25"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="4" alt="4"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f19">4</td><td class="ctr2" id="g20">4</td><td class="ctr1" id="h21">5</td><td class="ctr2" id="i21">5</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td><td class="ctr1" id="l19">1</td><td class="ctr2" id="m23">1</td></tr><tr><td id="a22"><a href="PipelineOperation.html" class="el_class">PipelineOperation</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="24" alt="24"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f18">6</td><td class="ctr2" id="g18">6</td><td class="ctr1" id="h17">8</td><td class="ctr2" id="i19">8</td><td class="ctr1" id="j10">6</td><td class="ctr2" id="k10">6</td><td class="ctr1" id="l20">1</td><td class="ctr2" id="m24">1</td></tr><tr><td id="a17"><a href="AuthenticationType.html" class="el_class">AuthenticationType</a></td><td class="bar" id="b25"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="15" alt="15"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d26"/><td class="ctr2" id="e26">n/a</td><td class="ctr1" id="f26">1</td><td class="ctr2" id="g26">1</td><td class="ctr1" id="h24">3</td><td class="ctr2" id="i24">3</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k25">1</td><td class="ctr1" id="l21">1</td><td class="ctr2" id="m25">1</td></tr><tr><td id="a14"><a href="ApplicationProperties$System.html" class="el_class">ApplicationProperties.System</a></td><td class="bar" id="b26"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="11" alt="11"/></td><td class="ctr2" id="c26">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="4" alt="4"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f23">3</td><td class="ctr2" id="g23">3</td><td class="ctr1" id="h26">1</td><td class="ctr2" id="i26">1</td><td class="ctr1" id="j26">1</td><td class="ctr2" id="k26">1</td><td class="ctr1" id="l22">1</td><td class="ctr2" id="m26">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.model/index.source.html b/testResult/html/stirling.software.SPDF.model/index.source.html
    new file mode 100644
    index 000000000..4f231ad0e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.model/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.model</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.model</span></div><h1>stirling.software.SPDF.model</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,472 of 1,506</td><td class="ctr2">2%</td><td class="bar">154 of 156</td><td class="ctr2">1%</td><td class="ctr1">216</td><td class="ctr2">220</td><td class="ctr1">318</td><td class="ctr2">326</td><td class="ctr1">137</td><td class="ctr2">141</td><td class="ctr1">23</td><td class="ctr2">27</td></tr></tfoot><tbody><tr><td id="a2"><a href="ApplicationProperties.java.html" class="el_source">ApplicationProperties.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="114" height="10" title="682" alt="682"/><img src="../jacoco-resources/greenbar.gif" width="5" height="10" title="34" alt="34"/></td><td class="ctr2" id="c0">4%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="118" height="10" title="132" alt="132"/><img src="../jacoco-resources/greenbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">1%</td><td class="ctr1" id="f0">113</td><td class="ctr2" id="g0">117</td><td class="ctr1" id="h0">110</td><td class="ctr2" id="i0">118</td><td class="ctr1" id="j0">45</td><td class="ctr2" id="k0">49</td><td class="ctr1" id="l0">10</td><td class="ctr2" id="m0">14</td></tr><tr><td id="a10"><a href="Role.java.html" class="el_source">Role.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="166" alt="166"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">12</td><td class="ctr2" id="g2">12</td><td class="ctr1" id="h2">28</td><td class="ctr2" id="i2">28</td><td class="ctr1" id="j2">9</td><td class="ctr2" id="k2">9</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a12"><a href="User.java.html" class="el_source">User.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="128" alt="128"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">28</td><td class="ctr2" id="g1">28</td><td class="ctr1" id="h1">41</td><td class="ctr2" id="i1">41</td><td class="ctr1" id="j1">24</td><td class="ctr2" id="k1">24</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a13"><a href="UsernameAttribute.java.html" class="el_source">UsernameAttribute.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="102" alt="102"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f11">2</td><td class="ctr2" id="g11">2</td><td class="ctr1" id="h4">17</td><td class="ctr2" id="i4">17</td><td class="ctr1" id="j11">2</td><td class="ctr2" id="k11">2</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a11"><a href="SortTypes.java.html" class="el_source">SortTypes.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="69" alt="69"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h10">12</td><td class="ctr2" id="i10">12</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a0"><a href="ApiEndpoint.java.html" class="el_source">ApiEndpoint.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="66" alt="66"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f6">7</td><td class="ctr2" id="g6">7</td><td class="ctr1" id="h5">17</td><td class="ctr2" id="i5">17</td><td class="ctr1" id="j10">5</td><td class="ctr2" id="k10">5</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a1"><a href="ApiKeyAuthenticationToken.java.html" class="el_source">ApiKeyAuthenticationToken.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="49" alt="49"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f7">7</td><td class="ctr2" id="g7">7</td><td class="ctr1" id="h3">19</td><td class="ctr2" id="i3">19</td><td class="ctr1" id="j6">6</td><td class="ctr2" id="k6">6</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a3"><a href="AttemptCounter.java.html" class="el_source">AttemptCounter.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="43" alt="43"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f8">7</td><td class="ctr2" id="g8">7</td><td class="ctr1" id="h7">13</td><td class="ctr2" id="i7">13</td><td class="ctr1" id="j7">6</td><td class="ctr2" id="k7">6</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a5"><a href="Authority.java.html" class="el_source">Authority.java</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="38" alt="38"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f5">8</td><td class="ctr2" id="g5">8</td><td class="ctr1" id="h6">15</td><td class="ctr2" id="i6">15</td><td class="ctr1" id="j5">8</td><td class="ctr2" id="k5">8</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a7"><a href="PersistentLogin.java.html" class="el_source">PersistentLogin.java</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="31" alt="31"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f3">9</td><td class="ctr2" id="g3">9</td><td class="ctr1" id="h8">13</td><td class="ctr2" id="i8">13</td><td class="ctr1" id="j3">9</td><td class="ctr2" id="k3">9</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a8"><a href="PipelineConfig.java.html" class="el_source">PipelineConfig.java</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="31" alt="31"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f4">9</td><td class="ctr2" id="g4">9</td><td class="ctr1" id="h9">13</td><td class="ctr2" id="i9">13</td><td class="ctr1" id="j4">9</td><td class="ctr2" id="k4">9</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a6"><a href="InputStreamTemplateResource.java.html" class="el_source">InputStreamTemplateResource.java</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="28" alt="28"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f9">6</td><td class="ctr2" id="g9">6</td><td class="ctr1" id="h11">9</td><td class="ctr2" id="i11">9</td><td class="ctr1" id="j8">6</td><td class="ctr2" id="k8">6</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a9"><a href="PipelineOperation.java.html" class="el_source">PipelineOperation.java</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="24" alt="24"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f10">6</td><td class="ctr2" id="g10">6</td><td class="ctr1" id="h12">8</td><td class="ctr2" id="i12">8</td><td class="ctr1" id="j9">6</td><td class="ctr2" id="k9">6</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a4"><a href="AuthenticationType.java.html" class="el_source">AuthenticationType.java</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="15" alt="15"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h13">3</td><td class="ctr2" id="i13">3</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m13">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.pdf/FlexibleCSVWriter.html b/testResult/html/stirling.software.SPDF.pdf/FlexibleCSVWriter.html
    new file mode 100644
    index 000000000..36166e7c4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.pdf/FlexibleCSVWriter.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FlexibleCSVWriter</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.pdf</a> &gt; <span class="el_class">FlexibleCSVWriter</span></div><h1>FlexibleCSVWriter</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">7 of 7</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="FlexibleCSVWriter.java.html#L14" class="el_method">FlexibleCSVWriter(CSVFormat)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="FlexibleCSVWriter.java.html#L10" class="el_method">FlexibleCSVWriter()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.pdf/FlexibleCSVWriter.java.html b/testResult/html/stirling.software.SPDF.pdf/FlexibleCSVWriter.java.html
    new file mode 100644
    index 000000000..39f54ec9a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.pdf/FlexibleCSVWriter.java.html
    @@ -0,0 +1,17 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FlexibleCSVWriter.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.pdf</a> &gt; <span class="el_source">FlexibleCSVWriter.java</span></div><h1>FlexibleCSVWriter.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.pdf;
    +
    +import org.apache.commons.csv.CSVFormat;
    +
    +import technology.tabula.writers.CSVWriter;
    +
    +public class FlexibleCSVWriter extends CSVWriter {
    +
    +    public FlexibleCSVWriter() {
    +<span class="nc" id="L10">        super();</span>
    +<span class="nc" id="L11">    }</span>
    +
    +    public FlexibleCSVWriter(CSVFormat csvFormat) {
    +<span class="nc" id="L14">        super(csvFormat);</span>
    +<span class="nc" id="L15">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.pdf/TextFinder$MatchInfo.html b/testResult/html/stirling.software.SPDF.pdf/TextFinder$MatchInfo.html
    new file mode 100644
    index 000000000..8845b6baa
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.pdf/TextFinder$MatchInfo.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TextFinder.MatchInfo</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.pdf</a> &gt; <span class="el_class">TextFinder.MatchInfo</span></div><h1>TextFinder.MatchInfo</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="TextFinder.java.html#L102" class="el_method">TextFinder.MatchInfo(TextFinder, int, int)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.pdf/TextFinder.html b/testResult/html/stirling.software.SPDF.pdf/TextFinder.html
    new file mode 100644
    index 000000000..a6addb930
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.pdf/TextFinder.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TextFinder</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.pdf</a> &gt; <span class="el_class">TextFinder</span></div><h1>TextFinder</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">201 of 201</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">42</td><td class="ctr2">42</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a4"><a href="TextFinder.java.html#L61" class="el_method">writeString(String, List)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="107" alt="107"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h0">18</td><td class="ctr2" id="i0">18</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="TextFinder.java.html#L34" class="el_method">findOccurrencesInText(String, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="54" alt="54"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h1">12</td><td class="ctr2" id="i1">12</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="TextFinder.java.html#L23" class="el_method">TextFinder(String, boolean, boolean)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="21" alt="21"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">7</td><td class="ctr2" id="i2">7</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="TextFinder.java.html#L87" class="el_method">getTextLocations(PDDocument)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="15" alt="15"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="TextFinder.java.html#L17" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.pdf/TextFinder.java.html b/testResult/html/stirling.software.SPDF.pdf/TextFinder.java.html
    new file mode 100644
    index 000000000..437a6055e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.pdf/TextFinder.java.html
    @@ -0,0 +1,108 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>TextFinder.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.pdf</a> &gt; <span class="el_source">TextFinder.java</span></div><h1>TextFinder.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.pdf;
    +
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.text.PDFTextStripper;
    +import org.apache.pdfbox.text.TextPosition;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.PDFText;
    +
    +<span class="nc" id="L17">@Slf4j</span>
    +public class TextFinder extends PDFTextStripper {
    +
    +    private final String searchText;
    +    private final boolean useRegex;
    +    private final boolean wholeWordSearch;
    +<span class="nc" id="L23">    private final List&lt;PDFText&gt; textOccurrences = new ArrayList&lt;&gt;();</span>
    +
    +    public TextFinder(String searchText, boolean useRegex, boolean wholeWordSearch)
    +<span class="nc" id="L26">            throws IOException {</span>
    +<span class="nc" id="L27">        this.searchText = searchText.toLowerCase();</span>
    +<span class="nc" id="L28">        this.useRegex = useRegex;</span>
    +<span class="nc" id="L29">        this.wholeWordSearch = wholeWordSearch;</span>
    +<span class="nc" id="L30">        setSortByPosition(true);</span>
    +<span class="nc" id="L31">    }</span>
    +
    +    private List&lt;MatchInfo&gt; findOccurrencesInText(String searchText, String content) {
    +<span class="nc" id="L34">        List&lt;MatchInfo&gt; matches = new ArrayList&lt;&gt;();</span>
    +
    +        Pattern pattern;
    +
    +<span class="nc bnc" id="L38" title="All 2 branches missed.">        if (useRegex) {</span>
    +            // Use regex-based search
    +            pattern =
    +<span class="nc bnc" id="L41" title="All 2 branches missed.">                    wholeWordSearch</span>
    +<span class="nc" id="L42">                            ? Pattern.compile(&quot;\\b&quot; + searchText + &quot;\\b&quot;)</span>
    +<span class="nc" id="L43">                            : Pattern.compile(searchText);</span>
    +        } else {
    +            // Use normal text search
    +            pattern =
    +<span class="nc bnc" id="L47" title="All 2 branches missed.">                    wholeWordSearch</span>
    +<span class="nc" id="L48">                            ? Pattern.compile(&quot;\\b&quot; + Pattern.quote(searchText) + &quot;\\b&quot;)</span>
    +<span class="nc" id="L49">                            : Pattern.compile(Pattern.quote(searchText));</span>
    +        }
    +
    +<span class="nc" id="L52">        Matcher matcher = pattern.matcher(content);</span>
    +<span class="nc bnc" id="L53" title="All 2 branches missed.">        while (matcher.find()) {</span>
    +<span class="nc" id="L54">            matches.add(new MatchInfo(matcher.start(), matcher.end() - matcher.start()));</span>
    +        }
    +<span class="nc" id="L56">        return matches;</span>
    +    }
    +
    +    @Override
    +    protected void writeString(String text, List&lt;TextPosition&gt; textPositions) {
    +<span class="nc bnc" id="L61" title="All 2 branches missed.">        for (MatchInfo match : findOccurrencesInText(searchText, text.toLowerCase())) {</span>
    +<span class="nc" id="L62">            int index = match.startIndex;</span>
    +<span class="nc bnc" id="L63" title="All 2 branches missed.">            if (index + match.matchLength &lt;= textPositions.size()) {</span>
    +                // Initial values based on the first character
    +<span class="nc" id="L65">                TextPosition first = textPositions.get(index);</span>
    +<span class="nc" id="L66">                float minX = first.getX();</span>
    +<span class="nc" id="L67">                float minY = first.getY();</span>
    +<span class="nc" id="L68">                float maxX = first.getX() + first.getWidth();</span>
    +<span class="nc" id="L69">                float maxY = first.getY() + first.getHeight();</span>
    +
    +                // Loop over the rest of the characters and adjust bounding box values
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">                for (int i = index; i &lt; index + match.matchLength; i++) {</span>
    +<span class="nc" id="L73">                    TextPosition position = textPositions.get(i);</span>
    +<span class="nc" id="L74">                    minX = Math.min(minX, position.getX());</span>
    +<span class="nc" id="L75">                    minY = Math.min(minY, position.getY());</span>
    +<span class="nc" id="L76">                    maxX = Math.max(maxX, position.getX() + position.getWidth());</span>
    +<span class="nc" id="L77">                    maxY = Math.max(maxY, position.getY() + position.getHeight());</span>
    +                }
    +
    +<span class="nc" id="L80">                textOccurrences.add(</span>
    +<span class="nc" id="L81">                        new PDFText(getCurrentPageNo() - 1, minX, minY, maxX, maxY, text));</span>
    +            }
    +<span class="nc" id="L83">        }</span>
    +<span class="nc" id="L84">    }</span>
    +
    +    public List&lt;PDFText&gt; getTextLocations(PDDocument document) throws Exception {
    +<span class="nc" id="L87">        this.getText(document);</span>
    +<span class="nc" id="L88">        log.debug(</span>
    +                &quot;Found &quot;
    +<span class="nc" id="L90">                        + textOccurrences.size()</span>
    +                        + &quot; occurrences of '&quot;
    +                        + searchText
    +                        + &quot;' in the document.&quot;);
    +
    +<span class="nc" id="L95">        return textOccurrences;</span>
    +    }
    +
    +    private class MatchInfo {
    +        int startIndex;
    +        int matchLength;
    +
    +<span class="nc" id="L102">        MatchInfo(int startIndex, int matchLength) {</span>
    +<span class="nc" id="L103">            this.startIndex = startIndex;</span>
    +<span class="nc" id="L104">            this.matchLength = matchLength;</span>
    +<span class="nc" id="L105">        }</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.pdf/index.html b/testResult/html/stirling.software.SPDF.pdf/index.html
    new file mode 100644
    index 000000000..f197e69f7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.pdf/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.pdf</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.pdf</span></div><h1>stirling.software.SPDF.pdf</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">220 of 220</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">50</td><td class="ctr2">50</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="TextFinder.html" class="el_class">TextFinder</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="201" alt="201"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">42</td><td class="ctr2" id="i0">42</td><td class="ctr1" id="j0">5</td><td class="ctr2" id="k0">5</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a2"><a href="TextFinder$MatchInfo.html" class="el_class">TextFinder.MatchInfo</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a0"><a href="FlexibleCSVWriter.html" class="el_class">FlexibleCSVWriter</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="7" alt="7"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j1">2</td><td class="ctr2" id="k1">2</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.pdf/index.source.html b/testResult/html/stirling.software.SPDF.pdf/index.source.html
    new file mode 100644
    index 000000000..2d141c086
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.pdf/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.pdf</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.pdf</span></div><h1>stirling.software.SPDF.pdf</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">220 of 220</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">50</td><td class="ctr2">50</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="TextFinder.java.html" class="el_source">TextFinder.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="213" alt="213"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">13</td><td class="ctr2" id="g0">13</td><td class="ctr1" id="h0">46</td><td class="ctr2" id="i0">46</td><td class="ctr1" id="j0">6</td><td class="ctr2" id="k0">6</td><td class="ctr1" id="l0">2</td><td class="ctr2" id="m0">2</td></tr><tr><td id="a0"><a href="FlexibleCSVWriter.java.html" class="el_source">FlexibleCSVWriter.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="7" alt="7"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">2</td><td class="ctr2" id="k1">2</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.repository/JPATokenRepositoryImpl.html b/testResult/html/stirling.software.SPDF.repository/JPATokenRepositoryImpl.html
    new file mode 100644
    index 000000000..0f1024e73
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.repository/JPATokenRepositoryImpl.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>JPATokenRepositoryImpl</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.repository</a> &gt; <span class="el_class">JPATokenRepositoryImpl</span></div><h1>JPATokenRepositoryImpl</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">85 of 85</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="JPATokenRepositoryImpl.java.html#L22" class="el_method">createNewToken(PersistentRememberMeToken)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="26" alt="26"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="JPATokenRepositoryImpl.java.html#L43" class="el_method">getTokenForSeries(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="110" height="10" title="24" alt="24"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="JPATokenRepositoryImpl.java.html#L33" class="el_method">updateToken(String, String, Date)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="101" height="10" title="22" alt="22"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="JPATokenRepositoryImpl.java.html#L55" class="el_method">removeUserTokens(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="7" alt="7"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="JPATokenRepositoryImpl.java.html#L15" class="el_method">JPATokenRepositoryImpl(PersistentLoginRepository)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">3</td><td class="ctr2" id="i4">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.repository/JPATokenRepositoryImpl.java.html b/testResult/html/stirling.software.SPDF.repository/JPATokenRepositoryImpl.java.html
    new file mode 100644
    index 000000000..d7fd66a48
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.repository/JPATokenRepositoryImpl.java.html
    @@ -0,0 +1,60 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>JPATokenRepositoryImpl.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.repository</a> &gt; <span class="el_source">JPATokenRepositoryImpl.java</span></div><h1>JPATokenRepositoryImpl.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.repository;
    +
    +import java.util.Date;
    +
    +import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken;
    +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
    +import org.springframework.transaction.annotation.Transactional;
    +
    +import stirling.software.SPDF.model.PersistentLogin;
    +
    +public class JPATokenRepositoryImpl implements PersistentTokenRepository {
    +
    +    private final PersistentLoginRepository persistentLoginRepository;
    +
    +<span class="nc" id="L15">    public JPATokenRepositoryImpl(PersistentLoginRepository persistentLoginRepository) {</span>
    +<span class="nc" id="L16">        this.persistentLoginRepository = persistentLoginRepository;</span>
    +<span class="nc" id="L17">    }</span>
    +
    +    @Override
    +    @Transactional
    +    public void createNewToken(PersistentRememberMeToken token) {
    +<span class="nc" id="L22">        PersistentLogin newToken = new PersistentLogin();</span>
    +<span class="nc" id="L23">        newToken.setSeries(token.getSeries());</span>
    +<span class="nc" id="L24">        newToken.setUsername(token.getUsername());</span>
    +<span class="nc" id="L25">        newToken.setToken(token.getTokenValue());</span>
    +<span class="nc" id="L26">        newToken.setLastUsed(token.getDate());</span>
    +<span class="nc" id="L27">        persistentLoginRepository.save(newToken);</span>
    +<span class="nc" id="L28">    }</span>
    +
    +    @Override
    +    @Transactional
    +    public void updateToken(String series, String tokenValue, Date lastUsed) {
    +<span class="nc" id="L33">        PersistentLogin existingToken = persistentLoginRepository.findById(series).orElse(null);</span>
    +<span class="nc bnc" id="L34" title="All 2 branches missed.">        if (existingToken != null) {</span>
    +<span class="nc" id="L35">            existingToken.setToken(tokenValue);</span>
    +<span class="nc" id="L36">            existingToken.setLastUsed(lastUsed);</span>
    +<span class="nc" id="L37">            persistentLoginRepository.save(existingToken);</span>
    +        }
    +<span class="nc" id="L39">    }</span>
    +
    +    @Override
    +    public PersistentRememberMeToken getTokenForSeries(String seriesId) {
    +<span class="nc" id="L43">        PersistentLogin token = persistentLoginRepository.findById(seriesId).orElse(null);</span>
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">        if (token != null) {</span>
    +<span class="nc" id="L45">            return new PersistentRememberMeToken(</span>
    +<span class="nc" id="L46">                    token.getUsername(), token.getSeries(), token.getToken(), token.getLastUsed());</span>
    +        }
    +<span class="nc" id="L48">        return null;</span>
    +    }
    +
    +    @Override
    +    @Transactional
    +    public void removeUserTokens(String username) {
    +        try {
    +<span class="nc" id="L55">            persistentLoginRepository.deleteByUsername(username);</span>
    +<span class="nc" id="L56">        } catch (Exception e) {</span>
    +<span class="nc" id="L57">        }</span>
    +<span class="nc" id="L58">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.repository/index.html b/testResult/html/stirling.software.SPDF.repository/index.html
    new file mode 100644
    index 000000000..7fb40569f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.repository/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.repository</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.repository</span></div><h1>stirling.software.SPDF.repository</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">85 of 85</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="JPATokenRepositoryImpl.html" class="el_class">JPATokenRepositoryImpl</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="85" alt="85"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h0">25</td><td class="ctr2" id="i0">25</td><td class="ctr1" id="j0">5</td><td class="ctr2" id="k0">5</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.repository/index.source.html b/testResult/html/stirling.software.SPDF.repository/index.source.html
    new file mode 100644
    index 000000000..666b0bae4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.repository/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.repository</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.repository</span></div><h1>stirling.software.SPDF.repository</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">85 of 85</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="JPATokenRepositoryImpl.java.html" class="el_source">JPATokenRepositoryImpl.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="85" alt="85"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h0">25</td><td class="ctr2" id="i0">25</td><td class="ctr1" id="j0">5</td><td class="ctr2" id="k0">5</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service.misc/ReplaceAndInvertColorService.html b/testResult/html/stirling.software.SPDF.service.misc/ReplaceAndInvertColorService.html
    new file mode 100644
    index 000000000..ea7a9320f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service.misc/ReplaceAndInvertColorService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvertColorService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service.misc</a> &gt; <span class="el_class">ReplaceAndInvertColorService</span></div><h1>ReplaceAndInvertColorService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">18 of 18</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvertColorService.java.html#L32" class="el_method">replaceAndInvertColor(MultipartFile, ReplaceAndInvert, HighContrastColorCombination, String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">3</td><td class="ctr2" id="i0">3</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ReplaceAndInvertColorService.java.html#L20" class="el_method">ReplaceAndInvertColorService(ReplaceAndInvertColorFactory)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">3</td><td class="ctr2" id="i1">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service.misc/ReplaceAndInvertColorService.java.html b/testResult/html/stirling.software.SPDF.service.misc/ReplaceAndInvertColorService.java.html
    new file mode 100644
    index 000000000..6a864d872
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service.misc/ReplaceAndInvertColorService.java.html
    @@ -0,0 +1,43 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvertColorService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service.misc</a> &gt; <span class="el_source">ReplaceAndInvertColorService.java</span></div><h1>ReplaceAndInvertColorService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service.misc;
    +
    +import java.io.IOException;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.core.io.InputStreamResource;
    +import org.springframework.stereotype.Service;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import stirling.software.SPDF.Factories.ReplaceAndInvertColorFactory;
    +import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
    +import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
    +import stirling.software.SPDF.utils.misc.ReplaceAndInvertColorStrategy;
    +
    +@Service
    +public class ReplaceAndInvertColorService {
    +    private ReplaceAndInvertColorFactory replaceAndInvertColorFactory;
    +
    +    @Autowired
    +<span class="nc" id="L20">    public ReplaceAndInvertColorService(ReplaceAndInvertColorFactory replaceAndInvertColorFactory) {</span>
    +<span class="nc" id="L21">        this.replaceAndInvertColorFactory = replaceAndInvertColorFactory;</span>
    +<span class="nc" id="L22">    }</span>
    +
    +    public InputStreamResource replaceAndInvertColor(
    +            MultipartFile file,
    +            ReplaceAndInvert replaceAndInvertOption,
    +            HighContrastColorCombination highContrastColorCombination,
    +            String backGroundColor,
    +            String textColor)
    +            throws IOException {
    +
    +<span class="nc" id="L32">        ReplaceAndInvertColorStrategy replaceColorStrategy =</span>
    +<span class="nc" id="L33">                replaceAndInvertColorFactory.replaceAndInvert(</span>
    +                        file,
    +                        replaceAndInvertOption,
    +                        highContrastColorCombination,
    +                        backGroundColor,
    +                        textColor);
    +
    +<span class="nc" id="L40">        return replaceColorStrategy.replace();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service.misc/index.html b/testResult/html/stirling.software.SPDF.service.misc/index.html
    new file mode 100644
    index 000000000..966c3ee1c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service.misc/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.service.misc</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.service.misc</span></div><h1>stirling.software.SPDF.service.misc</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">18 of 18</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvertColorService.html" class="el_class">ReplaceAndInvertColorService</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">2</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service.misc/index.source.html b/testResult/html/stirling.software.SPDF.service.misc/index.source.html
    new file mode 100644
    index 000000000..c5631dc36
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service.misc/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.service.misc</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.service.misc</span></div><h1>stirling.software.SPDF.service.misc</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">18 of 18</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvertColorService.java.html" class="el_source">ReplaceAndInvertColorService.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">2</td><td class="ctr2" id="k0">2</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/CertificateValidationService.html b/testResult/html/stirling.software.SPDF.service/CertificateValidationService.html
    new file mode 100644
    index 000000000..df4d2c5a1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/CertificateValidationService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CertificateValidationService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_class">CertificateValidationService</span></div><h1>CertificateValidationService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">259 of 259</td><td class="ctr2">0%</td><td class="bar">24 of 24</td><td class="ctr2">0%</td><td class="ctr1">21</td><td class="ctr2">21</td><td class="ctr1">73</td><td class="ctr2">73</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a3"><a href="CertificateValidationService.java.html#L27" class="el_method">loadMozillaCertificates()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="80" alt="80"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">22</td><td class="ctr2" id="i0">22</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="CertificateValidationService.java.html#L78" class="el_method">validateCertificateChain(X509Certificate)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="103" height="10" title="69" alt="69"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h1">17</td><td class="ctr2" id="i1">17</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="CertificateValidationService.java.html#L62" class="el_method">parseOctalData(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="41" alt="41"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h2">9</td><td class="ctr2" id="i2">9</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a7"><a href="CertificateValidationService.java.html#L103" class="el_method">validateTrustStore(X509Certificate)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="29" alt="29"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h3">9</td><td class="ctr2" id="i3">9</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="CertificateValidationService.java.html#L21" class="el_method">initializeTrustStore()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="12" alt="12"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="CertificateValidationService.java.html#L128" class="el_method">validateCertificateChainWithCustomCert(X509Certificate, X509Certificate)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="9" alt="9"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">4</td><td class="ctr2" id="i5">4</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a8"><a href="CertificateValidationService.java.html#L138" class="el_method">validateTrustWithCustomCert(X509Certificate, X509Certificate)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="9" alt="9"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="CertificateValidationService.java.html#L118" class="el_method">isRevoked(X509Certificate)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h6">4</td><td class="ctr2" id="i6">4</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a0"><a href="CertificateValidationService.java.html#L16" class="el_method">CertificateValidationService()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="3" alt="3"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/CertificateValidationService.java.html b/testResult/html/stirling.software.SPDF.service/CertificateValidationService.java.html
    new file mode 100644
    index 000000000..a3d80ed03
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/CertificateValidationService.java.html
    @@ -0,0 +1,144 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CertificateValidationService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_source">CertificateValidationService.java</span></div><h1>CertificateValidationService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service;
    +
    +import java.io.*;
    +import java.security.KeyStore;
    +import java.security.KeyStoreException;
    +import java.security.cert.*;
    +import java.util.*;
    +
    +import org.springframework.stereotype.Service;
    +
    +import io.github.pixee.security.BoundedLineReader;
    +
    +import jakarta.annotation.PostConstruct;
    +
    +@Service
    +<span class="nc" id="L16">public class CertificateValidationService {</span>
    +    private KeyStore trustStore;
    +
    +    @PostConstruct
    +    private void initializeTrustStore() throws Exception {
    +<span class="nc" id="L21">        trustStore = KeyStore.getInstance(KeyStore.getDefaultType());</span>
    +<span class="nc" id="L22">        trustStore.load(null, null);</span>
    +<span class="nc" id="L23">        loadMozillaCertificates();</span>
    +<span class="nc" id="L24">    }</span>
    +
    +    private void loadMozillaCertificates() throws Exception {
    +<span class="nc" id="L27">        try (InputStream is = getClass().getResourceAsStream(&quot;/certdata.txt&quot;)) {</span>
    +<span class="nc" id="L28">            BufferedReader reader = new BufferedReader(new InputStreamReader(is));</span>
    +            String line;
    +<span class="nc" id="L30">            StringBuilder certData = new StringBuilder();</span>
    +<span class="nc" id="L31">            boolean inCert = false;</span>
    +<span class="nc" id="L32">            int certCount = 0;</span>
    +
    +<span class="nc bnc" id="L34" title="All 2 branches missed.">            while ((line = BoundedLineReader.readLine(reader, 5_000_000)) != null) {</span>
    +<span class="nc bnc" id="L35" title="All 2 branches missed.">                if (line.startsWith(&quot;CKA_VALUE MULTILINE_OCTAL&quot;)) {</span>
    +<span class="nc" id="L36">                    inCert = true;</span>
    +<span class="nc" id="L37">                    certData = new StringBuilder();</span>
    +<span class="nc" id="L38">                    continue;</span>
    +                }
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">                if (inCert) {</span>
    +<span class="nc bnc" id="L41" title="All 2 branches missed.">                    if (&quot;END&quot;.equals(line)) {</span>
    +<span class="nc" id="L42">                        inCert = false;</span>
    +<span class="nc" id="L43">                        byte[] certBytes = parseOctalData(certData.toString());</span>
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">                        if (certBytes != null) {</span>
    +<span class="nc" id="L45">                            CertificateFactory cf = CertificateFactory.getInstance(&quot;X.509&quot;);</span>
    +<span class="nc" id="L46">                            X509Certificate cert =</span>
    +                                    (X509Certificate)
    +<span class="nc" id="L48">                                            cf.generateCertificate(</span>
    +                                                    new ByteArrayInputStream(certBytes));
    +<span class="nc" id="L50">                            trustStore.setCertificateEntry(&quot;mozilla-cert-&quot; + certCount++, cert);</span>
    +                        }
    +<span class="nc" id="L52">                    } else {</span>
    +<span class="nc" id="L53">                        certData.append(line).append(&quot;\n&quot;);</span>
    +                    }
    +                }
    +            }
    +        }
    +<span class="nc" id="L58">    }</span>
    +
    +    private byte[] parseOctalData(String data) {
    +        try {
    +<span class="nc" id="L62">            ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L63">            String[] tokens = data.split(&quot;\\\\&quot;);</span>
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">            for (String token : tokens) {</span>
    +<span class="nc" id="L65">                token = token.trim();</span>
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">                if (!token.isEmpty()) {</span>
    +<span class="nc" id="L67">                    baos.write(Integer.parseInt(token, 8));</span>
    +                }
    +            }
    +<span class="nc" id="L70">            return baos.toByteArray();</span>
    +<span class="nc" id="L71">        } catch (Exception e) {</span>
    +<span class="nc" id="L72">            return null;</span>
    +        }
    +    }
    +
    +    public boolean validateCertificateChain(X509Certificate cert) {
    +        try {
    +<span class="nc" id="L78">            CertPathValidator validator = CertPathValidator.getInstance(&quot;PKIX&quot;);</span>
    +<span class="nc" id="L79">            CertificateFactory cf = CertificateFactory.getInstance(&quot;X.509&quot;);</span>
    +<span class="nc" id="L80">            List&lt;X509Certificate&gt; certList = Arrays.asList(cert);</span>
    +<span class="nc" id="L81">            CertPath certPath = cf.generateCertPath(certList);</span>
    +
    +<span class="nc" id="L83">            Set&lt;TrustAnchor&gt; anchors = new HashSet&lt;&gt;();</span>
    +<span class="nc" id="L84">            Enumeration&lt;String&gt; aliases = trustStore.aliases();</span>
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">            while (aliases.hasMoreElements()) {</span>
    +<span class="nc" id="L86">                Object trustCert = trustStore.getCertificate(aliases.nextElement());</span>
    +<span class="nc bnc" id="L87" title="All 2 branches missed.">                if (trustCert instanceof X509Certificate x509Cert) {</span>
    +<span class="nc" id="L88">                    anchors.add(new TrustAnchor(x509Cert, null));</span>
    +                }
    +<span class="nc" id="L90">            }</span>
    +
    +<span class="nc" id="L92">            PKIXParameters params = new PKIXParameters(anchors);</span>
    +<span class="nc" id="L93">            params.setRevocationEnabled(false);</span>
    +<span class="nc" id="L94">            validator.validate(certPath, params);</span>
    +<span class="nc" id="L95">            return true;</span>
    +<span class="nc" id="L96">        } catch (Exception e) {</span>
    +<span class="nc" id="L97">            return false;</span>
    +        }
    +    }
    +
    +    public boolean validateTrustStore(X509Certificate cert) {
    +        try {
    +<span class="nc" id="L103">            Enumeration&lt;String&gt; aliases = trustStore.aliases();</span>
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">            while (aliases.hasMoreElements()) {</span>
    +<span class="nc" id="L105">                Object trustCert = trustStore.getCertificate(aliases.nextElement());</span>
    +<span class="nc bnc" id="L106" title="All 4 branches missed.">                if (trustCert instanceof X509Certificate &amp;&amp; cert.equals(trustCert)) {</span>
    +<span class="nc" id="L107">                    return true;</span>
    +                }
    +<span class="nc" id="L109">            }</span>
    +<span class="nc" id="L110">            return false;</span>
    +<span class="nc" id="L111">        } catch (KeyStoreException e) {</span>
    +<span class="nc" id="L112">            return false;</span>
    +        }
    +    }
    +
    +    public boolean isRevoked(X509Certificate cert) {
    +        try {
    +<span class="nc" id="L118">            cert.checkValidity();</span>
    +<span class="nc" id="L119">            return false;</span>
    +<span class="nc" id="L120">        } catch (CertificateExpiredException | CertificateNotYetValidException e) {</span>
    +<span class="nc" id="L121">            return true;</span>
    +        }
    +    }
    +
    +    public boolean validateCertificateChainWithCustomCert(
    +            X509Certificate cert, X509Certificate customCert) {
    +        try {
    +<span class="nc" id="L128">            cert.verify(customCert.getPublicKey());</span>
    +<span class="nc" id="L129">            return true;</span>
    +<span class="nc" id="L130">        } catch (Exception e) {</span>
    +<span class="nc" id="L131">            return false;</span>
    +        }
    +    }
    +
    +    public boolean validateTrustWithCustomCert(X509Certificate cert, X509Certificate customCert) {
    +        try {
    +            // Compare the issuer of the signature certificate with the custom certificate
    +<span class="nc" id="L138">            return cert.getIssuerX500Principal().equals(customCert.getSubjectX500Principal());</span>
    +<span class="nc" id="L139">        } catch (Exception e) {</span>
    +<span class="nc" id="L140">            return false;</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/CustomPDFDocumentFactory.html b/testResult/html/stirling.software.SPDF.service/CustomPDFDocumentFactory.html
    new file mode 100644
    index 000000000..1e7245425
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/CustomPDFDocumentFactory.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomPDFDocumentFactory</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_class">CustomPDFDocumentFactory</span></div><h1>CustomPDFDocumentFactory</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">760 of 769</td><td class="ctr2">1%</td><td class="bar">52 of 52</td><td class="ctr2">0%</td><td class="ctr1">69</td><td class="ctr2">70</td><td class="ctr1">163</td><td class="ctr2">165</td><td class="ctr1">43</td><td class="ctr2">44</td></tr></tfoot><tbody><tr><td id="a12"><a href="CustomPDFDocumentFactory.java.html#L237" class="el_method">getStreamCacheFunction(long)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="125" alt="125"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">24</td><td class="ctr2" id="i0">24</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a33"><a href="CustomPDFDocumentFactory.java.html#L301" class="el_method">loadAdaptivelyWithPassword(Object, long, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="59" alt="59"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h1">10</td><td class="ctr2" id="i1">10</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a32"><a href="CustomPDFDocumentFactory.java.html#L279" class="el_method">loadAdaptively(Object, long)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="57" alt="57"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h2">10</td><td class="ctr2" id="i2">10</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a21"><a href="CustomPDFDocumentFactory.java.html#L175" class="el_method">load(InputStream, String, boolean)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="36" alt="36"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h3">9</td><td class="ctr2" id="i3">9</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a19"><a href="CustomPDFDocumentFactory.java.html#L152" class="el_method">load(InputStream, boolean)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="35" alt="35"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h4">8</td><td class="ctr2" id="i4">8</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a35"><a href="CustomPDFDocumentFactory.java.html#L329" class="el_method">loadFromBytesWithPassword(byte[], long, RandomAccessStreamCache.StreamCacheCreateFunction, String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="32" alt="32"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f8">2</td><td class="ctr2" id="g8">2</td><td class="ctr1" id="h10">6</td><td class="ctr2" id="i10">6</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a34"><a href="CustomPDFDocumentFactory.java.html#L361" class="el_method">loadFromBytes(byte[], long, RandomAccessStreamCache.StreamCacheCreateFunction)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="31" alt="31"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h11">6</td><td class="ctr2" id="i11">6</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a27"><a href="CustomPDFDocumentFactory.java.html#L110" class="el_method">load(Path, boolean)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="30" alt="30"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f5">3</td><td class="ctr2" id="g5">3</td><td class="ctr1" id="h5">8</td><td class="ctr2" id="i5">8</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a15"><a href="CustomPDFDocumentFactory.java.html#L131" class="el_method">load(byte[], boolean)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="30" alt="30"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f6">3</td><td class="ctr2" id="g6">3</td><td class="ctr1" id="h6">8</td><td class="ctr2" id="i6">8</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a17"><a href="CustomPDFDocumentFactory.java.html#L83" class="el_method">load(File, boolean)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="29" alt="29"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f7">3</td><td class="ctr2" id="g7">3</td><td class="ctr1" id="h7">8</td><td class="ctr2" id="i7">8</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a42"><a href="CustomPDFDocumentFactory.java.html#L382" class="el_method">saveToBytes(PDDocument)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="27" alt="27"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="2" alt="2"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h8">7</td><td class="ctr2" id="i8">7</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a41"><a href="CustomPDFDocumentFactory.java.html#L397" class="el_method">removePassword(PDDocument)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="19" alt="19"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="2" alt="2"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f11">2</td><td class="ctr2" id="g11">2</td><td class="ctr1" id="h9">7</td><td class="ctr2" id="i9">7</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a10"><a href="CustomPDFDocumentFactory.java.html#L409" class="el_method">createTempFile(String)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="15" alt="15"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d12"/><td class="ctr2" id="e12">n/a</td><td class="ctr1" id="f12">1</td><td class="ctr2" id="g12">1</td><td class="ctr1" id="h13">3</td><td class="ctr2" id="i13">3</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a7"><a href="CustomPDFDocumentFactory.java.html#L457" class="el_method">createNewDocumentBasedOnOldDocument(PDDocument)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="14" alt="14"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f13">1</td><td class="ctr2" id="g13">1</td><td class="ctr1" id="h12">4</td><td class="ctr2" id="i12">4</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a4"><a href="CustomPDFDocumentFactory.java.html#L372" class="el_method">createNewDocument(MemoryUsageSetting)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="13" alt="13"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h14">3</td><td class="ctr2" id="i14">3</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a2"><a href="CustomPDFDocumentFactory.java.html#L435" class="el_method">createNewBytesBasedOnOldDocument(PDDocument)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="13" alt="13"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h15">3</td><td class="ctr2" id="i15">3</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a13"><a href="CustomPDFDocumentFactory.java.html#L342" class="el_method">lambda$createScratchFileCacheFunction$0(MemoryUsageSetting)</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="12" alt="12"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h16">3</td><td class="ctr2" id="i16">3</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a37"><a href="CustomPDFDocumentFactory.java.html#L322" class="el_method">loadFromFileWithPassword(File, long, RandomAccessStreamCache.StreamCacheCreateFunction, String)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h25">1</td><td class="ctr2" id="i26">1</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a36"><a href="CustomPDFDocumentFactory.java.html#L356" class="el_method">loadFromFile(File, long, RandomAccessStreamCache.StreamCacheCreateFunction)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h26">1</td><td class="ctr2" id="i27">1</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a0"><a href="CustomPDFDocumentFactory.java.html#L421" class="el_method">createNewBytesBasedOnOldDocument(byte[])</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h19">2</td><td class="ctr2" id="i19">2</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a1"><a href="CustomPDFDocumentFactory.java.html#L428" class="el_method">createNewBytesBasedOnOldDocument(File)</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">2</td><td class="ctr2" id="i20">2</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a5"><a href="CustomPDFDocumentFactory.java.html#L442" class="el_method">createNewDocumentBasedOnOldDocument(byte[])</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">2</td><td class="ctr2" id="i21">2</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a6"><a href="CustomPDFDocumentFactory.java.html#L449" class="el_method">createNewDocumentBasedOnOldDocument(File)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h22">2</td><td class="ctr2" id="i22">2</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a39"><a href="CustomPDFDocumentFactory.java.html#L465" class="el_method">loadToBytes(File)</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">2</td><td class="ctr2" id="i23">2</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a38"><a href="CustomPDFDocumentFactory.java.html#L472" class="el_method">loadToBytes(byte[])</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c25">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h24">2</td><td class="ctr2" id="i24">2</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr><tr><td id="a31"><a href="CustomPDFDocumentFactory.java.html#L198" class="el_method">load(String, boolean)</a></td><td class="bar" id="b25"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="8" alt="8"/></td><td class="ctr2" id="c26">0%</td><td class="bar" id="d25"/><td class="ctr2" id="e25">n/a</td><td class="ctr1" id="f25">1</td><td class="ctr2" id="g25">1</td><td class="ctr1" id="h27">1</td><td class="ctr2" id="i28">1</td><td class="ctr1" id="j25">1</td><td class="ctr2" id="k25">1</td></tr><tr><td id="a40"><a href="CustomPDFDocumentFactory.java.html#L350" class="el_method">postProcessDocument(PDDocument)</a></td><td class="bar" id="b26"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="8" alt="8"/></td><td class="ctr2" id="c27">0%</td><td class="bar" id="d26"/><td class="ctr2" id="e26">n/a</td><td class="ctr1" id="f26">1</td><td class="ctr2" id="g26">1</td><td class="ctr1" id="h17">3</td><td class="ctr2" id="i17">3</td><td class="ctr1" id="j26">1</td><td class="ctr2" id="k26">1</td></tr><tr><td id="a9"><a href="CustomPDFDocumentFactory.java.html#L416" class="el_method">createTempDirectory(String)</a></td><td class="bar" id="b27"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="8" alt="8"/></td><td class="ctr2" id="c28">0%</td><td class="bar" id="d27"/><td class="ctr2" id="e27">n/a</td><td class="ctr1" id="f27">1</td><td class="ctr2" id="g27">1</td><td class="ctr1" id="h28">1</td><td class="ctr2" id="i29">1</td><td class="ctr1" id="j27">1</td><td class="ctr2" id="k27">1</td></tr><tr><td id="a25"><a href="CustomPDFDocumentFactory.java.html#L229" class="el_method">load(MultipartFile, String, boolean)</a></td><td class="bar" id="b28"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="7" alt="7"/></td><td class="ctr2" id="c29">0%</td><td class="bar" id="d28"/><td class="ctr2" id="e28">n/a</td><td class="ctr1" id="f28">1</td><td class="ctr2" id="g28">1</td><td class="ctr1" id="h29">1</td><td class="ctr2" id="i30">1</td><td class="ctr1" id="j28">1</td><td class="ctr2" id="k28">1</td></tr><tr><td id="a11"><a href="CustomPDFDocumentFactory.java.html#L66" class="el_method">CustomPDFDocumentFactory(PdfMetadataService)</a></td><td class="bar" id="b29"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c30">0%</td><td class="bar" id="d29"/><td class="ctr2" id="e29">n/a</td><td class="ctr1" id="f29">1</td><td class="ctr2" id="g29">1</td><td class="ctr1" id="h18">3</td><td class="ctr2" id="i18">3</td><td class="ctr1" id="j29">1</td><td class="ctr2" id="k29">1</td></tr><tr><td id="a20"><a href="CustomPDFDocumentFactory.java.html#L169" class="el_method">load(InputStream, String)</a></td><td class="bar" id="b30"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c31">0%</td><td class="bar" id="d30"/><td class="ctr2" id="e30">n/a</td><td class="ctr1" id="f30">1</td><td class="ctr2" id="g30">1</td><td class="ctr1" id="h30">1</td><td class="ctr2" id="i31">1</td><td class="ctr1" id="j30">1</td><td class="ctr2" id="k30">1</td></tr><tr><td id="a29"><a href="CustomPDFDocumentFactory.java.html#L208" class="el_method">load(PDFFile, boolean)</a></td><td class="bar" id="b31"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c32">0%</td><td class="bar" id="d31"/><td class="ctr2" id="e31">n/a</td><td class="ctr1" id="f31">1</td><td class="ctr2" id="g31">1</td><td class="ctr1" id="h31">1</td><td class="ctr2" id="i32">1</td><td class="ctr1" id="j31">1</td><td class="ctr2" id="k31">1</td></tr><tr><td id="a23"><a href="CustomPDFDocumentFactory.java.html#L218" class="el_method">load(MultipartFile, boolean)</a></td><td class="bar" id="b32"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c33">0%</td><td class="bar" id="d32"/><td class="ctr2" id="e32">n/a</td><td class="ctr1" id="f32">1</td><td class="ctr2" id="g32">1</td><td class="ctr1" id="h32">1</td><td class="ctr2" id="i33">1</td><td class="ctr1" id="j32">1</td><td class="ctr2" id="k32">1</td></tr><tr><td id="a24"><a href="CustomPDFDocumentFactory.java.html#L223" class="el_method">load(MultipartFile, String)</a></td><td class="bar" id="b33"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="6" alt="6"/></td><td class="ctr2" id="c34">0%</td><td class="bar" id="d33"/><td class="ctr2" id="e33">n/a</td><td class="ctr1" id="f33">1</td><td class="ctr2" id="g33">1</td><td class="ctr1" id="h33">1</td><td class="ctr2" id="i34">1</td><td class="ctr1" id="j33">1</td><td class="ctr2" id="k33">1</td></tr><tr><td id="a16"><a href="CustomPDFDocumentFactory.java.html#L75" class="el_method">load(File)</a></td><td class="bar" id="b34"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c35">0%</td><td class="bar" id="d34"/><td class="ctr2" id="e34">n/a</td><td class="ctr1" id="f34">1</td><td class="ctr2" id="g34">1</td><td class="ctr1" id="h34">1</td><td class="ctr2" id="i35">1</td><td class="ctr1" id="j34">1</td><td class="ctr2" id="k34">1</td></tr><tr><td id="a26"><a href="CustomPDFDocumentFactory.java.html#L102" class="el_method">load(Path)</a></td><td class="bar" id="b35"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c36">0%</td><td class="bar" id="d35"/><td class="ctr2" id="e35">n/a</td><td class="ctr1" id="f35">1</td><td class="ctr2" id="g35">1</td><td class="ctr1" id="h35">1</td><td class="ctr2" id="i36">1</td><td class="ctr1" id="j35">1</td><td class="ctr2" id="k35">1</td></tr><tr><td id="a14"><a href="CustomPDFDocumentFactory.java.html#L126" class="el_method">load(byte[])</a></td><td class="bar" id="b36"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c37">0%</td><td class="bar" id="d36"/><td class="ctr2" id="e36">n/a</td><td class="ctr1" id="f36">1</td><td class="ctr2" id="g36">1</td><td class="ctr1" id="h36">1</td><td class="ctr2" id="i37">1</td><td class="ctr1" id="j36">1</td><td class="ctr2" id="k36">1</td></tr><tr><td id="a18"><a href="CustomPDFDocumentFactory.java.html#L147" class="el_method">load(InputStream)</a></td><td class="bar" id="b37"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c38">0%</td><td class="bar" id="d37"/><td class="ctr2" id="e37">n/a</td><td class="ctr1" id="f37">1</td><td class="ctr2" id="g37">1</td><td class="ctr1" id="h37">1</td><td class="ctr2" id="i38">1</td><td class="ctr1" id="j37">1</td><td class="ctr2" id="k37">1</td></tr><tr><td id="a30"><a href="CustomPDFDocumentFactory.java.html#L193" class="el_method">load(String)</a></td><td class="bar" id="b38"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c39">0%</td><td class="bar" id="d38"/><td class="ctr2" id="e38">n/a</td><td class="ctr1" id="f38">1</td><td class="ctr2" id="g38">1</td><td class="ctr1" id="h38">1</td><td class="ctr2" id="i39">1</td><td class="ctr1" id="j38">1</td><td class="ctr2" id="k38">1</td></tr><tr><td id="a28"><a href="CustomPDFDocumentFactory.java.html#L203" class="el_method">load(PDFFile)</a></td><td class="bar" id="b39"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c40">0%</td><td class="bar" id="d39"/><td class="ctr2" id="e39">n/a</td><td class="ctr1" id="f39">1</td><td class="ctr2" id="g39">1</td><td class="ctr1" id="h39">1</td><td class="ctr2" id="i40">1</td><td class="ctr1" id="j39">1</td><td class="ctr2" id="k39">1</td></tr><tr><td id="a22"><a href="CustomPDFDocumentFactory.java.html#L213" class="el_method">load(MultipartFile)</a></td><td class="bar" id="b40"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c41">0%</td><td class="bar" id="d40"/><td class="ctr2" id="e40">n/a</td><td class="ctr1" id="f40">1</td><td class="ctr2" id="g40">1</td><td class="ctr1" id="h40">1</td><td class="ctr2" id="i41">1</td><td class="ctr1" id="j40">1</td><td class="ctr2" id="k40">1</td></tr><tr><td id="a3"><a href="CustomPDFDocumentFactory.java.html#L378" class="el_method">createNewDocument()</a></td><td class="bar" id="b41"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c42">0%</td><td class="bar" id="d41"/><td class="ctr2" id="e41">n/a</td><td class="ctr1" id="f41">1</td><td class="ctr2" id="g41">1</td><td class="ctr1" id="h41">1</td><td class="ctr2" id="i42">1</td><td class="ctr1" id="j41">1</td><td class="ctr2" id="k41">1</td></tr><tr><td id="a8"><a href="CustomPDFDocumentFactory.java.html#L340" class="el_method">createScratchFileCacheFunction(MemoryUsageSetting)</a></td><td class="bar" id="b42"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="3" alt="3"/></td><td class="ctr2" id="c43">0%</td><td class="bar" id="d42"/><td class="ctr2" id="e42">n/a</td><td class="ctr1" id="f42">1</td><td class="ctr2" id="g42">1</td><td class="ctr1" id="h42">1</td><td class="ctr2" id="i43">1</td><td class="ctr1" id="j42">1</td><td class="ctr2" id="k42">1</td></tr><tr><td id="a43"><a href="CustomPDFDocumentFactory.java.html#L31" class="el_method">static {...}</a></td><td class="bar" id="b43"><img src="../jacoco-resources/greenbar.gif" width="8" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d43"/><td class="ctr2" id="e43">n/a</td><td class="ctr1" id="f43">0</td><td class="ctr2" id="g43">1</td><td class="ctr1" id="h43">0</td><td class="ctr2" id="i25">2</td><td class="ctr1" id="j43">0</td><td class="ctr2" id="k43">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/CustomPDFDocumentFactory.java.html b/testResult/html/stirling.software.SPDF.service/CustomPDFDocumentFactory.java.html
    new file mode 100644
    index 000000000..07fa3a5ac
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/CustomPDFDocumentFactory.java.html
    @@ -0,0 +1,477 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomPDFDocumentFactory.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_source">CustomPDFDocumentFactory.java</span></div><h1>CustomPDFDocumentFactory.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.File;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.StandardCopyOption;
    +import java.util.concurrent.atomic.AtomicLong;
    +
    +import org.apache.pdfbox.Loader;
    +import org.apache.pdfbox.examples.util.DeletingRandomAccessFile;
    +import org.apache.pdfbox.io.IOUtils;
    +import org.apache.pdfbox.io.MemoryUsageSetting;
    +import org.apache.pdfbox.io.RandomAccessStreamCache.StreamCacheCreateFunction;
    +import org.apache.pdfbox.io.ScratchFile;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.stereotype.Component;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +
    +/**
    + * Adaptive PDF document factory that optimizes memory usage based on file size and available system
    + * resources.
    + */
    +@Component
    +<span class="fc" id="L31">@Slf4j</span>
    +public class CustomPDFDocumentFactory {
    +
    +    private final PdfMetadataService pdfMetadataService;
    +
    +    // Memory thresholds and limits
    +
    +    private static final long SMALL_FILE_THRESHOLD = 10 * 1024 * 1024; // 10 MB
    +    // Files smaller than this threshold are loaded entirely in memory for better performance.
    +    // These files use IOUtils.createMemoryOnlyStreamCache() which keeps all document data in RAM.
    +    // No temp files are created for document data, reducing I/O operations but consuming more
    +    // memory.
    +
    +    private static final long LARGE_FILE_THRESHOLD = 50 * 1024 * 1024; // 50 MB
    +    // Files between SMALL and LARGE thresholds use file-based caching with ScratchFile,
    +    // but are loaded directly from byte arrays if provided that way.
    +    // When loading from byte arrays, once size exceeds this threshold, bytes are first
    +    // written to temp files before loading to reduce memory pressure.
    +
    +    private static final long LARGE_FILE_USAGE = 10 * 1024 * 1024;
    +
    +    private static final long EXTREMELY_LARGE_THRESHOLD = 100 * 1024 * 1024; // 100 MB
    +    // Files exceeding this threshold use specialized loading with RandomAccessReadBufferedFile
    +    // which provides buffered access to the file without loading the entire content at once.
    +    // These files are always processed using file-based caching with minimal memory footprint,
    +    // trading some performance for significantly reduced memory usage.
    +    // For extremely large PDFs, this prevents OutOfMemoryErrors at the cost of being more I/O
    +    // bound.
    +
    +    private static final double MIN_FREE_MEMORY_PERCENTAGE = 30.0; // 30%
    +    private static final long MIN_FREE_MEMORY_BYTES = 4L * 1024 * 1024 * 1024; // 4 GB
    +
    +    // Counter for tracking temporary resources
    +<span class="fc" id="L64">    private static final AtomicLong tempCounter = new AtomicLong(0);</span>
    +
    +<span class="nc" id="L66">    public CustomPDFDocumentFactory(PdfMetadataService pdfMetadataService) {</span>
    +<span class="nc" id="L67">        this.pdfMetadataService = pdfMetadataService;</span>
    +<span class="nc" id="L68">    }</span>
    +
    +    /**
    +     * Main entry point for loading a PDF document from a file. Automatically selects the most
    +     * appropriate loading strategy.
    +     */
    +    public PDDocument load(File file) throws IOException {
    +<span class="nc" id="L75">        return load(file, false);</span>
    +    }
    +
    +    /**
    +     * Main entry point for loading a PDF document from a file with read-only option. Automatically
    +     * selects the most appropriate loading strategy.
    +     */
    +    public PDDocument load(File file, boolean readOnly) throws IOException {
    +<span class="nc bnc" id="L83" title="All 2 branches missed.">        if (file == null) {</span>
    +<span class="nc" id="L84">            throw new IllegalArgumentException(&quot;File cannot be null&quot;);</span>
    +        }
    +
    +<span class="nc" id="L87">        long fileSize = file.length();</span>
    +<span class="nc" id="L88">        log.debug(&quot;Loading PDF from file, size: {}MB&quot;, fileSize / (1024 * 1024));</span>
    +
    +<span class="nc" id="L90">        PDDocument doc = loadAdaptively(file, fileSize);</span>
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">        if (!readOnly) {</span>
    +<span class="nc" id="L92">            postProcessDocument(doc);</span>
    +        }
    +<span class="nc" id="L94">        return doc;</span>
    +    }
    +
    +    /**
    +     * Main entry point for loading a PDF document from a Path. Automatically selects the most
    +     * appropriate loading strategy.
    +     */
    +    public PDDocument load(Path path) throws IOException {
    +<span class="nc" id="L102">        return load(path, false);</span>
    +    }
    +
    +    /**
    +     * Main entry point for loading a PDF document from a Path with read-only option. Automatically
    +     * selects the most appropriate loading strategy.
    +     */
    +    public PDDocument load(Path path, boolean readOnly) throws IOException {
    +<span class="nc bnc" id="L110" title="All 2 branches missed.">        if (path == null) {</span>
    +<span class="nc" id="L111">            throw new IllegalArgumentException(&quot;File cannot be null&quot;);</span>
    +        }
    +
    +<span class="nc" id="L114">        long fileSize = Files.size(path);</span>
    +<span class="nc" id="L115">        log.debug(&quot;Loading PDF from file, size: {}MB&quot;, fileSize / (1024 * 1024));</span>
    +
    +<span class="nc" id="L117">        PDDocument doc = loadAdaptively(path.toFile(), fileSize);</span>
    +<span class="nc bnc" id="L118" title="All 2 branches missed.">        if (!readOnly) {</span>
    +<span class="nc" id="L119">            postProcessDocument(doc);</span>
    +        }
    +<span class="nc" id="L121">        return doc;</span>
    +    }
    +
    +    /** Load a PDF from byte array with automatic optimization. */
    +    public PDDocument load(byte[] input) throws IOException {
    +<span class="nc" id="L126">        return load(input, false);</span>
    +    }
    +
    +    /** Load a PDF from byte array with automatic optimization and read-only option. */
    +    public PDDocument load(byte[] input, boolean readOnly) throws IOException {
    +<span class="nc bnc" id="L131" title="All 2 branches missed.">        if (input == null) {</span>
    +<span class="nc" id="L132">            throw new IllegalArgumentException(&quot;Input bytes cannot be null&quot;);</span>
    +        }
    +
    +<span class="nc" id="L135">        long dataSize = input.length;</span>
    +<span class="nc" id="L136">        log.debug(&quot;Loading PDF from byte array, size: {}MB&quot;, dataSize / (1024 * 1024));</span>
    +
    +<span class="nc" id="L138">        PDDocument doc = loadAdaptively(input, dataSize);</span>
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">        if (!readOnly) {</span>
    +<span class="nc" id="L140">            postProcessDocument(doc);</span>
    +        }
    +<span class="nc" id="L142">        return doc;</span>
    +    }
    +
    +    /** Load a PDF from InputStream with automatic optimization. */
    +    public PDDocument load(InputStream input) throws IOException {
    +<span class="nc" id="L147">        return load(input, false);</span>
    +    }
    +
    +    /** Load a PDF from InputStream with automatic optimization and read-only option. */
    +    public PDDocument load(InputStream input, boolean readOnly) throws IOException {
    +<span class="nc bnc" id="L152" title="All 2 branches missed.">        if (input == null) {</span>
    +<span class="nc" id="L153">            throw new IllegalArgumentException(&quot;InputStream cannot be null&quot;);</span>
    +        }
    +
    +        // Since we don't know the size upfront, buffer to a temp file
    +<span class="nc" id="L157">        Path tempFile = createTempFile(&quot;pdf-stream-&quot;);</span>
    +
    +<span class="nc" id="L159">        Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);</span>
    +<span class="nc" id="L160">        PDDocument doc = loadAdaptively(tempFile.toFile(), Files.size(tempFile));</span>
    +<span class="nc bnc" id="L161" title="All 2 branches missed.">        if (!readOnly) {</span>
    +<span class="nc" id="L162">            postProcessDocument(doc);</span>
    +        }
    +<span class="nc" id="L164">        return doc;</span>
    +    }
    +
    +    /** Load with password from InputStream */
    +    public PDDocument load(InputStream input, String password) throws IOException {
    +<span class="nc" id="L169">        return load(input, password, false);</span>
    +    }
    +
    +    /** Load with password from InputStream and read-only option */
    +    public PDDocument load(InputStream input, String password, boolean readOnly)
    +            throws IOException {
    +<span class="nc bnc" id="L175" title="All 2 branches missed.">        if (input == null) {</span>
    +<span class="nc" id="L176">            throw new IllegalArgumentException(&quot;InputStream cannot be null&quot;);</span>
    +        }
    +
    +        // Since we don't know the size upfront, buffer to a temp file
    +<span class="nc" id="L180">        Path tempFile = createTempFile(&quot;pdf-stream-&quot;);</span>
    +
    +<span class="nc" id="L182">        Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING);</span>
    +<span class="nc" id="L183">        PDDocument doc =</span>
    +<span class="nc" id="L184">                loadAdaptivelyWithPassword(tempFile.toFile(), Files.size(tempFile), password);</span>
    +<span class="nc bnc" id="L185" title="All 2 branches missed.">        if (!readOnly) {</span>
    +<span class="nc" id="L186">            postProcessDocument(doc);</span>
    +        }
    +<span class="nc" id="L188">        return doc;</span>
    +    }
    +
    +    /** Load from a file path string */
    +    public PDDocument load(String path) throws IOException {
    +<span class="nc" id="L193">        return load(path, false);</span>
    +    }
    +
    +    /** Load from a file path string with read-only option */
    +    public PDDocument load(String path, boolean readOnly) throws IOException {
    +<span class="nc" id="L198">        return load(new File(path), readOnly);</span>
    +    }
    +
    +    /** Load from a PDFFile object */
    +    public PDDocument load(PDFFile pdfFile) throws IOException {
    +<span class="nc" id="L203">        return load(pdfFile, false);</span>
    +    }
    +
    +    /** Load from a PDFFile object with read-only option */
    +    public PDDocument load(PDFFile pdfFile, boolean readOnly) throws IOException {
    +<span class="nc" id="L208">        return load(pdfFile.getFileInput(), readOnly);</span>
    +    }
    +
    +    /** Load from a MultipartFile */
    +    public PDDocument load(MultipartFile pdfFile) throws IOException {
    +<span class="nc" id="L213">        return load(pdfFile, false);</span>
    +    }
    +
    +    /** Load from a MultipartFile with read-only option */
    +    public PDDocument load(MultipartFile pdfFile, boolean readOnly) throws IOException {
    +<span class="nc" id="L218">        return load(pdfFile.getInputStream(), readOnly);</span>
    +    }
    +
    +    /** Load with password from MultipartFile */
    +    public PDDocument load(MultipartFile fileInput, String password) throws IOException {
    +<span class="nc" id="L223">        return load(fileInput, password, false);</span>
    +    }
    +
    +    /** Load with password from MultipartFile with read-only option */
    +    public PDDocument load(MultipartFile fileInput, String password, boolean readOnly)
    +            throws IOException {
    +<span class="nc" id="L229">        return load(fileInput.getInputStream(), password, readOnly);</span>
    +    }
    +
    +    /**
    +     * Determine the appropriate caching strategy based on file size and available memory. This
    +     * common method is used by both password and non-password loading paths.
    +     */
    +    public StreamCacheCreateFunction getStreamCacheFunction(long contentSize) {
    +<span class="nc" id="L237">        long maxMemory = Runtime.getRuntime().maxMemory();</span>
    +<span class="nc" id="L238">        long freeMemory = Runtime.getRuntime().freeMemory();</span>
    +<span class="nc" id="L239">        long totalMemory = Runtime.getRuntime().totalMemory();</span>
    +<span class="nc" id="L240">        long usedMemory = totalMemory - freeMemory;</span>
    +
    +        // Calculate percentage of free memory
    +<span class="nc" id="L243">        double freeMemoryPercent = (double) (maxMemory - usedMemory) / maxMemory * 100;</span>
    +<span class="nc" id="L244">        long actualFreeMemory = maxMemory - usedMemory;</span>
    +
    +        // Log memory status
    +<span class="nc" id="L247">        log.debug(</span>
    +                &quot;Memory status - Free: {}MB ({}%), Used: {}MB, Max: {}MB&quot;,
    +<span class="nc" id="L249">                actualFreeMemory / (1024 * 1024),</span>
    +<span class="nc" id="L250">                String.format(&quot;%.2f&quot;, freeMemoryPercent),</span>
    +<span class="nc" id="L251">                usedMemory / (1024 * 1024),</span>
    +<span class="nc" id="L252">                maxMemory / (1024 * 1024));</span>
    +
    +        // If free memory is critically low, always use file-based caching
    +<span class="nc bnc" id="L255" title="All 4 branches missed.">        if (freeMemoryPercent &lt; MIN_FREE_MEMORY_PERCENTAGE</span>
    +                || actualFreeMemory &lt; MIN_FREE_MEMORY_BYTES) {
    +<span class="nc" id="L257">            log.debug(</span>
    +                    &quot;Low memory detected ({}%), forcing file-based cache&quot;,
    +<span class="nc" id="L259">                    String.format(&quot;%.2f&quot;, freeMemoryPercent));</span>
    +<span class="nc" id="L260">            return createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly());</span>
    +<span class="nc bnc" id="L261" title="All 2 branches missed.">        } else if (contentSize &lt; SMALL_FILE_THRESHOLD) {</span>
    +<span class="nc" id="L262">            log.debug(&quot;Using memory-only cache for small document ({}KB)&quot;, contentSize / 1024);</span>
    +<span class="nc" id="L263">            return IOUtils.createMemoryOnlyStreamCache();</span>
    +<span class="nc bnc" id="L264" title="All 2 branches missed.">        } else if (contentSize &lt; LARGE_FILE_THRESHOLD) {</span>
    +            // For medium files (10-50MB), use a mixed approach
    +<span class="nc" id="L266">            log.debug(</span>
    +                    &quot;Using mixed memory/file cache for medium document ({}MB)&quot;,
    +<span class="nc" id="L268">                    contentSize / (1024 * 1024));</span>
    +<span class="nc" id="L269">            return createScratchFileCacheFunction(MemoryUsageSetting.setupMixed(LARGE_FILE_USAGE));</span>
    +        } else {
    +<span class="nc" id="L271">            log.debug(&quot;Using file-based cache for large document&quot;);</span>
    +<span class="nc" id="L272">            return createScratchFileCacheFunction(MemoryUsageSetting.setupTempFileOnly());</span>
    +        }
    +    }
    +
    +    /** Update the existing loadAdaptively method to use the common function */
    +    private PDDocument loadAdaptively(Object source, long contentSize) throws IOException {
    +        // Get the appropriate caching strategy
    +<span class="nc" id="L279">        StreamCacheCreateFunction cacheFunction = getStreamCacheFunction(contentSize);</span>
    +
    +        // If small handle as bytes and remove original file
    +<span class="nc bnc" id="L282" title="All 4 branches missed.">        if (contentSize &lt;= SMALL_FILE_THRESHOLD &amp;&amp; source instanceof File file) {</span>
    +<span class="nc" id="L283">            source = Files.readAllBytes(file.toPath());</span>
    +<span class="nc" id="L284">            file.delete();</span>
    +        }
    +        PDDocument document;
    +<span class="nc bnc" id="L287" title="All 2 branches missed.">        if (source instanceof File file) {</span>
    +<span class="nc" id="L288">            document = loadFromFile(file, contentSize, cacheFunction);</span>
    +<span class="nc bnc" id="L289" title="All 2 branches missed.">        } else if (source instanceof byte[] bytes) {</span>
    +<span class="nc" id="L290">            document = loadFromBytes(bytes, contentSize, cacheFunction);</span>
    +        } else {
    +<span class="nc" id="L292">            throw new IllegalArgumentException(&quot;Unsupported source type: &quot; + source.getClass());</span>
    +        }
    +<span class="nc" id="L294">        return document;</span>
    +    }
    +
    +    /** Load a PDF with password protection using adaptive loading strategies */
    +    private PDDocument loadAdaptivelyWithPassword(Object source, long contentSize, String password)
    +            throws IOException {
    +        // Get the appropriate caching strategy
    +<span class="nc" id="L301">        StreamCacheCreateFunction cacheFunction = getStreamCacheFunction(contentSize);</span>
    +        // If small handle as bytes and remove original file
    +<span class="nc bnc" id="L303" title="All 4 branches missed.">        if (contentSize &lt;= SMALL_FILE_THRESHOLD &amp;&amp; source instanceof File file) {</span>
    +<span class="nc" id="L304">            source = Files.readAllBytes(file.toPath());</span>
    +<span class="nc" id="L305">            file.delete();</span>
    +        }
    +        PDDocument document;
    +<span class="nc bnc" id="L308" title="All 2 branches missed.">        if (source instanceof File file) {</span>
    +<span class="nc" id="L309">            document = loadFromFileWithPassword(file, contentSize, cacheFunction, password);</span>
    +<span class="nc bnc" id="L310" title="All 2 branches missed.">        } else if (source instanceof byte[] bytes) {</span>
    +<span class="nc" id="L311">            document = loadFromBytesWithPassword(bytes, contentSize, cacheFunction, password);</span>
    +        } else {
    +<span class="nc" id="L313">            throw new IllegalArgumentException(&quot;Unsupported source type: &quot; + source.getClass());</span>
    +        }
    +<span class="nc" id="L315">        return document;</span>
    +    }
    +
    +    /** Load a file with password */
    +    private PDDocument loadFromFileWithPassword(
    +            File file, long size, StreamCacheCreateFunction cache, String password)
    +            throws IOException {
    +<span class="nc" id="L322">        return Loader.loadPDF(new DeletingRandomAccessFile(file), password, null, null, cache);</span>
    +    }
    +
    +    /** Load bytes with password */
    +    private PDDocument loadFromBytesWithPassword(
    +            byte[] bytes, long size, StreamCacheCreateFunction cache, String password)
    +            throws IOException {
    +<span class="nc bnc" id="L329" title="All 2 branches missed.">        if (size &gt;= SMALL_FILE_THRESHOLD) {</span>
    +<span class="nc" id="L330">            log.debug(&quot;Writing large byte array to temp file for password-protected PDF&quot;);</span>
    +<span class="nc" id="L331">            Path tempFile = createTempFile(&quot;pdf-bytes-&quot;);</span>
    +
    +<span class="nc" id="L333">            Files.write(tempFile, bytes);</span>
    +<span class="nc" id="L334">            return Loader.loadPDF(tempFile.toFile(), password, null, null, cache);</span>
    +        }
    +<span class="nc" id="L336">        return Loader.loadPDF(bytes, password, null, null, cache);</span>
    +    }
    +
    +    private StreamCacheCreateFunction createScratchFileCacheFunction(MemoryUsageSetting settings) {
    +<span class="nc" id="L340">        return () -&gt; {</span>
    +            try {
    +<span class="nc" id="L342">                return new ScratchFile(settings);</span>
    +<span class="nc" id="L343">            } catch (IOException e) {</span>
    +<span class="nc" id="L344">                throw new RuntimeException(&quot;ScratchFile initialization failed&quot;, e);</span>
    +            }
    +        };
    +    }
    +
    +    private void postProcessDocument(PDDocument doc) throws IOException {
    +<span class="nc" id="L350">        pdfMetadataService.setDefaultMetadata(doc);</span>
    +<span class="nc" id="L351">        removePassword(doc);</span>
    +<span class="nc" id="L352">    }</span>
    +
    +    private PDDocument loadFromFile(File file, long size, StreamCacheCreateFunction cache)
    +            throws IOException {
    +<span class="nc" id="L356">        return Loader.loadPDF(new DeletingRandomAccessFile(file), &quot;&quot;, null, null, cache);</span>
    +    }
    +
    +    private PDDocument loadFromBytes(byte[] bytes, long size, StreamCacheCreateFunction cache)
    +            throws IOException {
    +<span class="nc bnc" id="L361" title="All 2 branches missed.">        if (size &gt;= SMALL_FILE_THRESHOLD) {</span>
    +<span class="nc" id="L362">            log.debug(&quot;Writing large byte array to temp file&quot;);</span>
    +<span class="nc" id="L363">            Path tempFile = createTempFile(&quot;pdf-bytes-&quot;);</span>
    +
    +<span class="nc" id="L365">            Files.write(tempFile, bytes);</span>
    +<span class="nc" id="L366">            return loadFromFile(tempFile.toFile(), size, cache);</span>
    +        }
    +<span class="nc" id="L368">        return Loader.loadPDF(bytes, &quot;&quot;, null, null, cache);</span>
    +    }
    +
    +    public PDDocument createNewDocument(MemoryUsageSetting settings) throws IOException {
    +<span class="nc" id="L372">        PDDocument doc = new PDDocument(createScratchFileCacheFunction(settings));</span>
    +<span class="nc" id="L373">        pdfMetadataService.setDefaultMetadata(doc);</span>
    +<span class="nc" id="L374">        return doc;</span>
    +    }
    +
    +    public PDDocument createNewDocument() throws IOException {
    +<span class="nc" id="L378">        return createNewDocument(MemoryUsageSetting.setupTempFileOnly());</span>
    +    }
    +
    +    public byte[] saveToBytes(PDDocument document) throws IOException {
    +<span class="nc bnc" id="L382" title="All 2 branches missed.">        if (document.getNumberOfPages() &lt; 10) { // Simple heuristic</span>
    +<span class="nc" id="L383">            try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {</span>
    +<span class="nc" id="L384">                document.save(baos);</span>
    +<span class="nc" id="L385">                return baos.toByteArray();</span>
    +            }
    +        } else {
    +<span class="nc" id="L388">            Path tempFile = createTempFile(&quot;pdf-save-&quot;);</span>
    +
    +<span class="nc" id="L390">            document.save(tempFile.toFile());</span>
    +<span class="nc" id="L391">            return Files.readAllBytes(tempFile);</span>
    +        }
    +    }
    +
    +    // Improved password handling
    +    private void removePassword(PDDocument document) throws IOException {
    +<span class="nc bnc" id="L397" title="All 2 branches missed.">        if (document.isEncrypted()) {</span>
    +            try {
    +<span class="nc" id="L399">                document.setAllSecurityToBeRemoved(true);</span>
    +<span class="nc" id="L400">            } catch (Exception e) {</span>
    +<span class="nc" id="L401">                log.error(&quot;Decryption failed&quot;, e);</span>
    +<span class="nc" id="L402">                throw new IOException(&quot;PDF decryption failed&quot;, e);</span>
    +<span class="nc" id="L403">            }</span>
    +        }
    +<span class="nc" id="L405">    }</span>
    +
    +    // Temp file handling with enhanced logging
    +    private Path createTempFile(String prefix) throws IOException {
    +<span class="nc" id="L409">        Path file = Files.createTempFile(prefix + tempCounter.incrementAndGet() + &quot;-&quot;, &quot;.tmp&quot;);</span>
    +<span class="nc" id="L410">        log.debug(&quot;Created temp file: {}&quot;, file);</span>
    +<span class="nc" id="L411">        return file;</span>
    +    }
    +
    +    /** Create a uniquely named temporary directory */
    +    private Path createTempDirectory(String prefix) throws IOException {
    +<span class="nc" id="L416">        return Files.createTempDirectory(prefix + tempCounter.incrementAndGet() + &quot;-&quot;);</span>
    +    }
    +
    +    /** Create new document bytes based on an existing document */
    +    public byte[] createNewBytesBasedOnOldDocument(byte[] oldDocument) throws IOException {
    +<span class="nc" id="L421">        try (PDDocument document = load(oldDocument)) {</span>
    +<span class="nc" id="L422">            return saveToBytes(document);</span>
    +        }
    +    }
    +
    +    /** Create new document bytes based on an existing document file */
    +    public byte[] createNewBytesBasedOnOldDocument(File oldDocument) throws IOException {
    +<span class="nc" id="L428">        try (PDDocument document = load(oldDocument)) {</span>
    +<span class="nc" id="L429">            return saveToBytes(document);</span>
    +        }
    +    }
    +
    +    /** Create new document bytes based on an existing PDDocument */
    +    public byte[] createNewBytesBasedOnOldDocument(PDDocument oldDocument) throws IOException {
    +<span class="nc" id="L435">        pdfMetadataService.setMetadataToPdf(</span>
    +<span class="nc" id="L436">                oldDocument, pdfMetadataService.extractMetadataFromPdf(oldDocument), true);</span>
    +<span class="nc" id="L437">        return saveToBytes(oldDocument);</span>
    +    }
    +
    +    /** Create a new document based on an existing document bytes */
    +    public PDDocument createNewDocumentBasedOnOldDocument(byte[] oldDocument) throws IOException {
    +<span class="nc" id="L442">        try (PDDocument document = load(oldDocument)) {</span>
    +<span class="nc" id="L443">            return createNewDocumentBasedOnOldDocument(document);</span>
    +        }
    +    }
    +
    +    /** Create a new document based on an existing document file */
    +    public PDDocument createNewDocumentBasedOnOldDocument(File oldDocument) throws IOException {
    +<span class="nc" id="L449">        try (PDDocument document = load(oldDocument)) {</span>
    +<span class="nc" id="L450">            return createNewDocumentBasedOnOldDocument(document);</span>
    +        }
    +    }
    +
    +    /** Create a new document based on an existing PDDocument */
    +    public PDDocument createNewDocumentBasedOnOldDocument(PDDocument oldDocument)
    +            throws IOException {
    +<span class="nc" id="L457">        PDDocument document = createNewDocument();</span>
    +<span class="nc" id="L458">        pdfMetadataService.setMetadataToPdf(</span>
    +<span class="nc" id="L459">                document, pdfMetadataService.extractMetadataFromPdf(oldDocument), true);</span>
    +<span class="nc" id="L460">        return document;</span>
    +    }
    +
    +    /** Load document from a file and convert it to bytes */
    +    public byte[] loadToBytes(File file) throws IOException {
    +<span class="nc" id="L465">        try (PDDocument document = load(file)) {</span>
    +<span class="nc" id="L466">            return saveToBytes(document);</span>
    +        }
    +    }
    +
    +    /** Load document from bytes and convert it back to bytes */
    +    public byte[] loadToBytes(byte[] bytes) throws IOException {
    +<span class="nc" id="L472">        try (PDDocument document = load(bytes)) {</span>
    +<span class="nc" id="L473">            return saveToBytes(document);</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/LanguageService.html b/testResult/html/stirling.software.SPDF.service/LanguageService.html
    new file mode 100644
    index 000000000..5dc65fede
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/LanguageService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LanguageService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_class">LanguageService</span></div><h1>LanguageService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">89 of 89</td><td class="ctr2">0%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">24</td><td class="ctr2">24</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a0"><a href="LanguageService.java.html#L31" class="el_method">getSupportedLanguages()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="29" alt="29"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h0">11</td><td class="ctr2" id="i0">11</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="LanguageService.java.html#L44" class="el_method">lambda$getSupportedLanguages$2(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="95" height="10" title="23" alt="23"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="LanguageService.java.html#L38" class="el_method">lambda$getSupportedLanguages$0(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="57" height="10" title="14" alt="14"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="LanguageService.java.html#L22" class="el_method">LanguageService(ApplicationProperties)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="11" alt="11"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="LanguageService.java.html#L41" class="el_method">lambda$getSupportedLanguages$1(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="8" alt="8"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="LanguageService.java.html#L18" class="el_method">static {...}</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="4" alt="4"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/LanguageService.java.html b/testResult/html/stirling.software.SPDF.service/LanguageService.java.html
    new file mode 100644
    index 000000000..5abf32af3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/LanguageService.java.html
    @@ -0,0 +1,58 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LanguageService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_source">LanguageService.java</span></div><h1>LanguageService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service;
    +
    +import java.io.IOException;
    +import java.util.Arrays;
    +import java.util.HashSet;
    +import java.util.Set;
    +import java.util.stream.Collectors;
    +
    +import org.springframework.core.io.Resource;
    +import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    +import org.springframework.stereotype.Service;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +@Service
    +<span class="nc" id="L18">@Slf4j</span>
    +public class LanguageService {
    +
    +    private final ApplicationProperties applicationProperties;
    +<span class="nc" id="L22">    private final PathMatchingResourcePatternResolver resourcePatternResolver =</span>
    +            new PathMatchingResourcePatternResolver();
    +
    +<span class="nc" id="L25">    public LanguageService(ApplicationProperties applicationProperties) {</span>
    +<span class="nc" id="L26">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L27">    }</span>
    +
    +    public Set&lt;String&gt; getSupportedLanguages() {
    +        try {
    +<span class="nc" id="L31">            Resource[] resources =</span>
    +<span class="nc" id="L32">                    resourcePatternResolver.getResources(&quot;classpath*:messages_*.properties&quot;);</span>
    +
    +<span class="nc" id="L34">            return Arrays.stream(resources)</span>
    +<span class="nc" id="L35">                    .map(Resource::getFilename)</span>
    +<span class="nc" id="L36">                    .filter(</span>
    +                            filename -&gt;
    +<span class="nc bnc" id="L38" title="All 2 branches missed.">                                    filename != null</span>
    +<span class="nc bnc" id="L39" title="All 2 branches missed.">                                            &amp;&amp; filename.startsWith(&quot;messages_&quot;)</span>
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">                                            &amp;&amp; filename.endsWith(&quot;.properties&quot;))</span>
    +<span class="nc" id="L41">                    .map(filename -&gt; filename.replace(&quot;messages_&quot;, &quot;&quot;).replace(&quot;.properties&quot;, &quot;&quot;))</span>
    +<span class="nc" id="L42">                    .filter(</span>
    +                            languageCode -&gt; {
    +<span class="nc" id="L44">                                Set&lt;String&gt; allowedLanguages =</span>
    +<span class="nc" id="L45">                                        new HashSet&lt;&gt;(applicationProperties.getUi().getLanguages());</span>
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">                                return allowedLanguages.isEmpty()</span>
    +<span class="nc bnc" id="L47" title="All 2 branches missed.">                                        || allowedLanguages.contains(languageCode)</span>
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">                                        || &quot;en_GB&quot;.equals(languageCode);</span>
    +                            })
    +<span class="nc" id="L50">                    .collect(Collectors.toSet());</span>
    +
    +<span class="nc" id="L52">        } catch (IOException e) {</span>
    +<span class="nc" id="L53">            log.error(&quot;Error retrieving supported languages&quot;, e);</span>
    +<span class="nc" id="L54">            return new HashSet&lt;&gt;();</span>
    +        }
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/MetricsAggregatorService.html b/testResult/html/stirling.software.SPDF.service/MetricsAggregatorService.html
    new file mode 100644
    index 000000000..2492dc39b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/MetricsAggregatorService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsAggregatorService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_class">MetricsAggregatorService</span></div><h1>MetricsAggregatorService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">169 of 169</td><td class="ctr2">0%</td><td class="bar">28 of 28</td><td class="ctr2">0%</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">43</td><td class="ctr2">43</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="MetricsAggregatorService.java.html#L47" class="el_method">lambda$aggregateAndSendMetrics$0(boolean, Map, Counter)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="115" alt="115"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">13</td><td class="ctr2" id="g0">13</td><td class="ctr1" id="h0">27</td><td class="ctr2" id="i0">27</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="MetricsAggregatorService.java.html#L39" class="el_method">aggregateAndSendMetrics()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="34" height="10" title="33" alt="33"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">9</td><td class="ctr2" id="i1">9</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="MetricsAggregatorService.java.html#L25" class="el_method">MetricsAggregatorService(MeterRegistry, PostHogService, EndpointInspector)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="17" alt="17"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="MetricsAggregatorService.java.html#L20" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/MetricsAggregatorService.java.html b/testResult/html/stirling.software.SPDF.service/MetricsAggregatorService.java.html
    new file mode 100644
    index 000000000..d3459435a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/MetricsAggregatorService.java.html
    @@ -0,0 +1,99 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>MetricsAggregatorService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_source">MetricsAggregatorService.java</span></div><h1>MetricsAggregatorService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service;
    +
    +import java.util.HashMap;
    +import java.util.Map;
    +import java.util.concurrent.ConcurrentHashMap;
    +
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.scheduling.annotation.Scheduled;
    +import org.springframework.stereotype.Service;
    +
    +import io.micrometer.core.instrument.MeterRegistry;
    +import io.micrometer.core.instrument.search.Search;
    +
    +import stirling.software.SPDF.config.EndpointInspector;
    +
    +@Service
    +public class MetricsAggregatorService {
    +<span class="nc" id="L20">    private static final Logger logger = LoggerFactory.getLogger(MetricsAggregatorService.class);</span>
    +
    +    private final MeterRegistry meterRegistry;
    +    private final PostHogService postHogService;
    +    private final EndpointInspector endpointInspector;
    +<span class="nc" id="L25">    private final Map&lt;String, Double&gt; lastSentMetrics = new ConcurrentHashMap&lt;&gt;();</span>
    +
    +    @Autowired
    +    public MetricsAggregatorService(
    +            MeterRegistry meterRegistry,
    +            PostHogService postHogService,
    +<span class="nc" id="L31">            EndpointInspector endpointInspector) {</span>
    +<span class="nc" id="L32">        this.meterRegistry = meterRegistry;</span>
    +<span class="nc" id="L33">        this.postHogService = postHogService;</span>
    +<span class="nc" id="L34">        this.endpointInspector = endpointInspector;</span>
    +<span class="nc" id="L35">    }</span>
    +
    +    @Scheduled(fixedRate = 7200000) // Run every 2 hours
    +    public void aggregateAndSendMetrics() {
    +<span class="nc" id="L39">        Map&lt;String, Object&gt; metrics = new HashMap&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L41" title="All 2 branches missed.">        final boolean validateGetEndpoints = endpointInspector.getValidGetEndpoints().size() != 0;</span>
    +<span class="nc" id="L42">        Search.in(meterRegistry)</span>
    +<span class="nc" id="L43">                .name(&quot;http.requests&quot;)</span>
    +<span class="nc" id="L44">                .counters()</span>
    +<span class="nc" id="L45">                .forEach(</span>
    +                        counter -&gt; {
    +<span class="nc" id="L47">                            String method = counter.getId().getTag(&quot;method&quot;);</span>
    +<span class="nc" id="L48">                            String uri = counter.getId().getTag(&quot;uri&quot;);</span>
    +                            // Skip if either method or uri is null
    +<span class="nc bnc" id="L50" title="All 4 branches missed.">                            if (method == null || uri == null) {</span>
    +<span class="nc" id="L51">                                return;</span>
    +                            }
    +
    +                            // Skip URIs that are 2 characters or shorter
    +<span class="nc bnc" id="L55" title="All 2 branches missed.">                            if (uri.length() &lt;= 2) {</span>
    +<span class="nc" id="L56">                                return;</span>
    +                            }
    +
    +                            // Skip non-GET and non-POST requests
    +<span class="nc bnc" id="L60" title="All 4 branches missed.">                            if (!&quot;GET&quot;.equals(method) &amp;&amp; !&quot;POST&quot;.equals(method)) {</span>
    +<span class="nc" id="L61">                                return;</span>
    +                            }
    +
    +                            // For POST requests, only include if they start with /api/v1
    +<span class="nc bnc" id="L65" title="All 4 branches missed.">                            if (&quot;POST&quot;.equals(method) &amp;&amp; !uri.contains(&quot;api/v1&quot;)) {</span>
    +<span class="nc" id="L66">                                return;</span>
    +                            }
    +
    +<span class="nc bnc" id="L69" title="All 2 branches missed.">                            if (uri.contains(&quot;.txt&quot;)) {</span>
    +<span class="nc" id="L70">                                return;</span>
    +                            }
    +                            // For GET requests, validate if we have a list of valid endpoints
    +<span class="nc bnc" id="L73" title="All 4 branches missed.">                            if (&quot;GET&quot;.equals(method)</span>
    +                                    &amp;&amp; validateGetEndpoints
    +<span class="nc bnc" id="L75" title="All 2 branches missed.">                                    &amp;&amp; !endpointInspector.isValidGetEndpoint(uri)) {</span>
    +<span class="nc" id="L76">                                logger.debug(&quot;Skipping invalid GET endpoint: {}&quot;, uri);</span>
    +<span class="nc" id="L77">                                return;</span>
    +                            }
    +
    +<span class="nc" id="L80">                            String key =</span>
    +<span class="nc" id="L81">                                    String.format(</span>
    +<span class="nc" id="L82">                                            &quot;http_requests_%s_%s&quot;, method, uri.replace(&quot;/&quot;, &quot;_&quot;));</span>
    +<span class="nc" id="L83">                            double currentCount = counter.count();</span>
    +<span class="nc" id="L84">                            double lastCount = lastSentMetrics.getOrDefault(key, 0.0);</span>
    +<span class="nc" id="L85">                            double difference = currentCount - lastCount;</span>
    +<span class="nc bnc" id="L86" title="All 2 branches missed.">                            if (difference &gt; 0) {</span>
    +<span class="nc" id="L87">                                logger.info(&quot;{}, {}&quot;, key, difference);</span>
    +<span class="nc" id="L88">                                metrics.put(key, difference);</span>
    +<span class="nc" id="L89">                                lastSentMetrics.put(key, currentCount);</span>
    +                            }
    +<span class="nc" id="L91">                        });</span>
    +        // Send aggregated metrics to PostHog
    +<span class="nc bnc" id="L93" title="All 2 branches missed.">        if (!metrics.isEmpty()) {</span>
    +
    +<span class="nc" id="L95">            postHogService.captureEvent(&quot;aggregated_metrics&quot;, metrics);</span>
    +        }
    +<span class="nc" id="L97">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/PdfImageRemovalService.html b/testResult/html/stirling.software.SPDF.service/PdfImageRemovalService.html
    new file mode 100644
    index 000000000..190191988
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/PdfImageRemovalService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfImageRemovalService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_class">PdfImageRemovalService</span></div><h1>PdfImageRemovalService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">60 of 60</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="PdfImageRemovalService.java.html#L30" class="el_method">removeImagesFromPdf(PDDocument)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="57" alt="57"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PdfImageRemovalService.java.html#L16" class="el_method">PdfImageRemovalService()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/PdfImageRemovalService.java.html b/testResult/html/stirling.software.SPDF.service/PdfImageRemovalService.java.html
    new file mode 100644
    index 000000000..f0b304fa9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/PdfImageRemovalService.java.html
    @@ -0,0 +1,52 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfImageRemovalService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_source">PdfImageRemovalService.java</span></div><h1>PdfImageRemovalService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service;
    +
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +import org.apache.pdfbox.cos.COSName;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDResources;
    +import org.apache.pdfbox.pdmodel.graphics.PDXObject;
    +import org.springframework.stereotype.Service;
    +
    +/** Service class responsible for removing image objects from a PDF document. */
    +@Service
    +<span class="nc" id="L16">public class PdfImageRemovalService {</span>
    +
    +    /**
    +     * Removes all image objects from the provided PDF document.
    +     *
    +     * &lt;p&gt;This method iterates over each page in the document and removes any image XObjects found
    +     * in the page's resources.
    +     *
    +     * @param document The PDF document from which images will be removed.
    +     * @return The modified PDF document with images removed.
    +     * @throws IOException If an error occurs while processing the PDF document.
    +     */
    +    public PDDocument removeImagesFromPdf(PDDocument document) throws IOException {
    +        // Iterate over each page in the PDF document
    +<span class="nc bnc" id="L30" title="All 2 branches missed.">        for (PDPage page : document.getPages()) {</span>
    +<span class="nc" id="L31">            PDResources resources = page.getResources();</span>
    +            // Collect the XObject names to remove
    +<span class="nc" id="L33">            List&lt;COSName&gt; namesToRemove = new ArrayList&lt;&gt;();</span>
    +
    +            // Iterate over all XObject names in the page's resources
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">            for (COSName name : resources.getXObjectNames()) {</span>
    +                // Check if the XObject is an image
    +<span class="nc bnc" id="L38" title="All 2 branches missed.">                if (resources.isImageXObject(name)) {</span>
    +                    // Collect the name for removal
    +<span class="nc" id="L40">                    namesToRemove.add(name);</span>
    +                }
    +<span class="nc" id="L42">            }</span>
    +
    +            // Now, modify the resources by removing the collected names
    +<span class="nc bnc" id="L45" title="All 2 branches missed.">            for (COSName name : namesToRemove) {</span>
    +<span class="nc" id="L46">                resources.put(name, (PDXObject) null);</span>
    +<span class="nc" id="L47">            }</span>
    +<span class="nc" id="L48">        }</span>
    +<span class="nc" id="L49">        return document;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/PdfMetadataService.html b/testResult/html/stirling.software.SPDF.service/PdfMetadataService.html
    new file mode 100644
    index 000000000..4d4d8cd0b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/PdfMetadataService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfMetadataService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_class">PdfMetadataService</span></div><h1>PdfMetadataService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">174 of 174</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">61</td><td class="ctr2">61</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a2"><a href="PdfMetadataService.java.html#L88" class="el_method">setCommonMetadata(PDDocument, PdfMetadata)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="61" alt="61"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">21</td><td class="ctr2" id="i0">21</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PdfMetadataService.java.html#L35" class="el_method">extractMetadataFromPdf(PDDocument)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="35" alt="35"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">10</td><td class="ctr2" id="i2">10</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a6"><a href="PdfMetadataService.java.html#L65" class="el_method">setNewDocumentMetadata(PDDocument, PdfMetadata)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="34" alt="34"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">15</td><td class="ctr2" id="i1">15</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="PdfMetadataService.java.html#L27" class="el_method">PdfMetadataService(ApplicationProperties, String, boolean, UserServiceInterface)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="15" alt="15"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">6</td><td class="ctr2" id="i3">6</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="PdfMetadataService.java.html#L57" class="el_method">setMetadataToPdf(PDDocument, PdfMetadata, boolean)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="14" alt="14"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="PdfMetadataService.java.html#L48" class="el_method">setDefaultMetadata(PDDocument)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="9" alt="9"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="PdfMetadataService.java.html#L53" class="el_method">setMetadataToPdf(PDDocument, PdfMetadata)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="6" alt="6"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">2</td><td class="ctr2" id="i6">2</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/PdfMetadataService.java.html b/testResult/html/stirling.software.SPDF.service/PdfMetadataService.java.html
    new file mode 100644
    index 000000000..f0816318d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/PdfMetadataService.java.html
    @@ -0,0 +1,116 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfMetadataService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_source">PdfMetadataService.java</span></div><h1>PdfMetadataService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service;
    +
    +import java.util.Calendar;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.stereotype.Service;
    +
    +import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.model.PdfMetadata;
    +
    +@Service
    +public class PdfMetadataService {
    +
    +    private final ApplicationProperties applicationProperties;
    +    private final String stirlingPDFLabel;
    +    private final UserServiceInterface userService;
    +    private final boolean runningProOrHigher;
    +
    +    @Autowired
    +    public PdfMetadataService(
    +            ApplicationProperties applicationProperties,
    +            @Qualifier(&quot;StirlingPDFLabel&quot;) String stirlingPDFLabel,
    +            @Qualifier(&quot;runningProOrHigher&quot;) boolean runningProOrHigher,
    +<span class="nc" id="L27">            @Autowired(required = false) UserServiceInterface userService) {</span>
    +<span class="nc" id="L28">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L29">        this.stirlingPDFLabel = stirlingPDFLabel;</span>
    +<span class="nc" id="L30">        this.userService = userService;</span>
    +<span class="nc" id="L31">        this.runningProOrHigher = runningProOrHigher;</span>
    +<span class="nc" id="L32">    }</span>
    +
    +    public PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
    +<span class="nc" id="L35">        return PdfMetadata.builder()</span>
    +<span class="nc" id="L36">                .author(pdf.getDocumentInformation().getAuthor())</span>
    +<span class="nc" id="L37">                .producer(pdf.getDocumentInformation().getProducer())</span>
    +<span class="nc" id="L38">                .title(pdf.getDocumentInformation().getTitle())</span>
    +<span class="nc" id="L39">                .creator(pdf.getDocumentInformation().getCreator())</span>
    +<span class="nc" id="L40">                .subject(pdf.getDocumentInformation().getSubject())</span>
    +<span class="nc" id="L41">                .keywords(pdf.getDocumentInformation().getKeywords())</span>
    +<span class="nc" id="L42">                .creationDate(pdf.getDocumentInformation().getCreationDate())</span>
    +<span class="nc" id="L43">                .modificationDate(pdf.getDocumentInformation().getModificationDate())</span>
    +<span class="nc" id="L44">                .build();</span>
    +    }
    +
    +    public void setDefaultMetadata(PDDocument pdf) {
    +<span class="nc" id="L48">        PdfMetadata metadata = extractMetadataFromPdf(pdf);</span>
    +<span class="nc" id="L49">        setMetadataToPdf(pdf, metadata);</span>
    +<span class="nc" id="L50">    }</span>
    +
    +    public void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata) {
    +<span class="nc" id="L53">        setMetadataToPdf(pdf, pdfMetadata, false);</span>
    +<span class="nc" id="L54">    }</span>
    +
    +    public void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata, boolean newlyCreated) {
    +<span class="nc bnc" id="L57" title="All 4 branches missed.">        if (newlyCreated || pdfMetadata.getCreationDate() == null) {</span>
    +<span class="nc" id="L58">            setNewDocumentMetadata(pdf, pdfMetadata);</span>
    +        }
    +<span class="nc" id="L60">        setCommonMetadata(pdf, pdfMetadata);</span>
    +<span class="nc" id="L61">    }</span>
    +
    +    private void setNewDocumentMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
    +
    +<span class="nc" id="L65">        String creator = stirlingPDFLabel;</span>
    +
    +<span class="nc" id="L67">        if (applicationProperties</span>
    +<span class="nc" id="L68">                        .getPremium()</span>
    +<span class="nc" id="L69">                        .getProFeatures()</span>
    +<span class="nc" id="L70">                        .getCustomMetadata()</span>
    +<span class="nc bnc" id="L71" title="All 4 branches missed.">                        .isAutoUpdateMetadata()</span>
    +                &amp;&amp; runningProOrHigher) {
    +
    +<span class="nc" id="L74">            creator =</span>
    +                    applicationProperties
    +<span class="nc" id="L76">                            .getPremium()</span>
    +<span class="nc" id="L77">                            .getProFeatures()</span>
    +<span class="nc" id="L78">                            .getCustomMetadata()</span>
    +<span class="nc" id="L79">                            .getCreator();</span>
    +<span class="nc" id="L80">            pdf.getDocumentInformation().setProducer(stirlingPDFLabel);</span>
    +        }
    +
    +<span class="nc" id="L83">        pdf.getDocumentInformation().setCreator(creator);</span>
    +<span class="nc" id="L84">        pdf.getDocumentInformation().setCreationDate(Calendar.getInstance());</span>
    +<span class="nc" id="L85">    }</span>
    +
    +    private void setCommonMetadata(PDDocument pdf, PdfMetadata pdfMetadata) {
    +<span class="nc" id="L88">        String title = pdfMetadata.getTitle();</span>
    +<span class="nc" id="L89">        pdf.getDocumentInformation().setTitle(title);</span>
    +<span class="nc" id="L90">        pdf.getDocumentInformation().setProducer(stirlingPDFLabel);</span>
    +<span class="nc" id="L91">        pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());</span>
    +<span class="nc" id="L92">        pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());</span>
    +<span class="nc" id="L93">        pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());</span>
    +
    +<span class="nc" id="L95">        String author = pdfMetadata.getAuthor();</span>
    +<span class="nc" id="L96">        if (applicationProperties</span>
    +<span class="nc" id="L97">                        .getPremium()</span>
    +<span class="nc" id="L98">                        .getProFeatures()</span>
    +<span class="nc" id="L99">                        .getCustomMetadata()</span>
    +<span class="nc bnc" id="L100" title="All 4 branches missed.">                        .isAutoUpdateMetadata()</span>
    +                &amp;&amp; runningProOrHigher) {
    +<span class="nc" id="L102">            author =</span>
    +                    applicationProperties
    +<span class="nc" id="L104">                            .getPremium()</span>
    +<span class="nc" id="L105">                            .getProFeatures()</span>
    +<span class="nc" id="L106">                            .getCustomMetadata()</span>
    +<span class="nc" id="L107">                            .getAuthor();</span>
    +
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">            if (userService != null) {</span>
    +<span class="nc" id="L110">                author = author.replace(&quot;username&quot;, userService.getCurrentUsername());</span>
    +            }
    +        }
    +<span class="nc" id="L113">        pdf.getDocumentInformation().setAuthor(author);</span>
    +<span class="nc" id="L114">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/PostHogService.html b/testResult/html/stirling.software.SPDF.service/PostHogService.html
    new file mode 100644
    index 000000000..e1b8aa8d0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/PostHogService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PostHogService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_class">PostHogService</span></div><h1>PostHogService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">873 of 873</td><td class="ctr2">0%</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="ctr1">31</td><td class="ctr2">31</td><td class="ctr1">210</td><td class="ctr2">210</td><td class="ctr1">10</td><td class="ctr2">10</td></tr></tfoot><tbody><tr><td id="a3"><a href="PostHogService.java.html#L70" class="el_method">captureServerMetrics()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="317" alt="317"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h1">59</td><td class="ctr2" id="i1">59</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="PostHogService.java.html#L221" class="el_method">captureApplicationProperties()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="114" height="10" title="302" alt="302"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h0">82</td><td class="ctr2" id="i0">82</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="PostHogService.java.html#L173" class="el_method">getDockerMetrics()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="85" alt="85"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h2">19</td><td class="ctr2" id="i2">19</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="PostHogService.java.html#L384" class="el_method">getMacAddress()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="49" alt="49"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="6" alt="6"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h3">13</td><td class="ctr2" id="i3">13</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a7"><a href="PostHogService.java.html#L403" class="el_method">getNetworkInterfacesInfo()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="31" alt="31"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h4">10</td><td class="ctr2" id="i4">10</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a9"><a href="PostHogService.java.html#L40" class="el_method">PostHogService(PostHog, String, boolean, String, ApplicationProperties, UserServiceInterface, Environment)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="26" alt="26"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h5">10</td><td class="ctr2" id="i5">10</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a0"><a href="PostHogService.java.html#L209" class="el_method">addIfNotEmpty(Map, String, Object)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="24" alt="24"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="6" alt="6"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f3">4</td><td class="ctr2" id="g3">4</td><td class="ctr1" id="h6">6</td><td class="ctr2" id="i6">6</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a4"><a href="PostHogService.java.html#L52" class="el_method">captureSystemInfo()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="17" alt="17"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">2</td><td class="ctr2" id="g6">2</td><td class="ctr1" id="h7">6</td><td class="ctr2" id="i7">6</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a2"><a href="PostHogService.java.html#L63" class="el_method">captureEvent(String, Map)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="14" alt="14"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">2</td><td class="ctr2" id="g7">2</td><td class="ctr1" id="h8">4</td><td class="ctr2" id="i8">4</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a8"><a href="PostHogService.java.html#L169" class="el_method">isRunningInDocker()</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="8" alt="8"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/PostHogService.java.html b/testResult/html/stirling.software.SPDF.service/PostHogService.java.html
    new file mode 100644
    index 000000000..57afd50de
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/PostHogService.java.html
    @@ -0,0 +1,416 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PostHogService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_source">PostHogService.java</span></div><h1>PostHogService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service;
    +
    +import java.io.File;
    +import java.lang.management.*;
    +import java.net.InetAddress;
    +import java.net.NetworkInterface;
    +import java.nio.file.Files;
    +import java.nio.file.Paths;
    +import java.util.*;
    +
    +import org.apache.commons.lang3.StringUtils;
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.core.env.Environment;
    +import org.springframework.stereotype.Service;
    +
    +import com.posthog.java.PostHog;
    +
    +import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +@Service
    +public class PostHogService {
    +    private final PostHog postHog;
    +    private final String uniqueId;
    +    private final String appVersion;
    +    private final ApplicationProperties applicationProperties;
    +    private final UserServiceInterface userService;
    +    private final Environment env;
    +    private boolean configDirMounted;
    +
    +    @Autowired
    +    public PostHogService(
    +            PostHog postHog,
    +            @Qualifier(&quot;UUID&quot;) String uuid,
    +            @Qualifier(&quot;configDirMounted&quot;) boolean configDirMounted,
    +            @Qualifier(&quot;appVersion&quot;) String appVersion,
    +            ApplicationProperties applicationProperties,
    +            @Autowired(required = false) UserServiceInterface userService,
    +<span class="nc" id="L40">            Environment env) {</span>
    +<span class="nc" id="L41">        this.postHog = postHog;</span>
    +<span class="nc" id="L42">        this.uniqueId = uuid;</span>
    +<span class="nc" id="L43">        this.appVersion = appVersion;</span>
    +<span class="nc" id="L44">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L45">        this.userService = userService;</span>
    +<span class="nc" id="L46">        this.env = env;</span>
    +<span class="nc" id="L47">        this.configDirMounted = configDirMounted;</span>
    +<span class="nc" id="L48">        captureSystemInfo();</span>
    +<span class="nc" id="L49">    }</span>
    +
    +    private void captureSystemInfo() {
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">        if (!applicationProperties.getSystem().isAnalyticsEnabled()) {</span>
    +<span class="nc" id="L53">            return;</span>
    +        }
    +        try {
    +<span class="nc" id="L56">            postHog.capture(uniqueId, &quot;system_info_captured&quot;, captureServerMetrics());</span>
    +<span class="nc" id="L57">        } catch (Exception e) {</span>
    +            // Handle exceptions
    +<span class="nc" id="L59">        }</span>
    +<span class="nc" id="L60">    }</span>
    +
    +    public void captureEvent(String eventName, Map&lt;String, Object&gt; properties) {
    +<span class="nc bnc" id="L63" title="All 2 branches missed.">        if (!applicationProperties.getSystem().isAnalyticsEnabled()) {</span>
    +<span class="nc" id="L64">            return;</span>
    +        }
    +<span class="nc" id="L66">        postHog.capture(uniqueId, eventName, properties);</span>
    +<span class="nc" id="L67">    }</span>
    +
    +    public Map&lt;String, Object&gt; captureServerMetrics() {
    +<span class="nc" id="L70">        Map&lt;String, Object&gt; metrics = new HashMap&lt;&gt;();</span>
    +
    +        try {
    +            // Application version
    +<span class="nc" id="L74">            metrics.put(&quot;app_version&quot;, appVersion);</span>
    +<span class="nc" id="L75">            String deploymentType = &quot;JAR&quot;; // default</span>
    +<span class="nc bnc" id="L76" title="All 2 branches missed.">            if (&quot;true&quot;.equalsIgnoreCase(env.getProperty(&quot;BROWSER_OPEN&quot;))) {</span>
    +<span class="nc" id="L77">                deploymentType = &quot;EXE&quot;;</span>
    +<span class="nc bnc" id="L78" title="All 2 branches missed.">            } else if (isRunningInDocker()) {</span>
    +<span class="nc" id="L79">                deploymentType = &quot;DOCKER&quot;;</span>
    +            }
    +<span class="nc" id="L81">            metrics.put(&quot;deployment_type&quot;, deploymentType);</span>
    +<span class="nc" id="L82">            metrics.put(&quot;mounted_config_dir&quot;, configDirMounted);</span>
    +
    +            // System info
    +<span class="nc" id="L85">            metrics.put(&quot;os_name&quot;, System.getProperty(&quot;os.name&quot;));</span>
    +<span class="nc" id="L86">            metrics.put(&quot;os_version&quot;, System.getProperty(&quot;os.version&quot;));</span>
    +<span class="nc" id="L87">            metrics.put(&quot;java_version&quot;, System.getProperty(&quot;java.version&quot;));</span>
    +<span class="nc" id="L88">            metrics.put(&quot;user_name&quot;, System.getProperty(&quot;user.name&quot;));</span>
    +<span class="nc" id="L89">            metrics.put(&quot;user_home&quot;, System.getProperty(&quot;user.home&quot;));</span>
    +<span class="nc" id="L90">            metrics.put(&quot;user_dir&quot;, System.getProperty(&quot;user.dir&quot;));</span>
    +
    +            // CPU and Memory
    +<span class="nc" id="L93">            metrics.put(&quot;cpu_cores&quot;, Runtime.getRuntime().availableProcessors());</span>
    +<span class="nc" id="L94">            metrics.put(&quot;total_memory&quot;, Runtime.getRuntime().totalMemory());</span>
    +<span class="nc" id="L95">            metrics.put(&quot;free_memory&quot;, Runtime.getRuntime().freeMemory());</span>
    +
    +            // Network and Server Identity
    +<span class="nc" id="L98">            InetAddress localHost = InetAddress.getLocalHost();</span>
    +<span class="nc" id="L99">            metrics.put(&quot;ip_address&quot;, localHost.getHostAddress());</span>
    +<span class="nc" id="L100">            metrics.put(&quot;hostname&quot;, localHost.getHostName());</span>
    +<span class="nc" id="L101">            metrics.put(&quot;mac_address&quot;, getMacAddress());</span>
    +
    +            // JVM info
    +<span class="nc" id="L104">            metrics.put(&quot;jvm_vendor&quot;, System.getProperty(&quot;java.vendor&quot;));</span>
    +<span class="nc" id="L105">            metrics.put(&quot;jvm_version&quot;, System.getProperty(&quot;java.vm.version&quot;));</span>
    +
    +            // Locale and Timezone
    +<span class="nc" id="L108">            metrics.put(&quot;system_language&quot;, System.getProperty(&quot;user.language&quot;));</span>
    +<span class="nc" id="L109">            metrics.put(&quot;system_country&quot;, System.getProperty(&quot;user.country&quot;));</span>
    +<span class="nc" id="L110">            metrics.put(&quot;timezone&quot;, TimeZone.getDefault().getID());</span>
    +<span class="nc" id="L111">            metrics.put(&quot;locale&quot;, Locale.getDefault().toString());</span>
    +
    +            // Disk info
    +<span class="nc" id="L114">            File root = new File(&quot;.&quot;);</span>
    +<span class="nc" id="L115">            metrics.put(&quot;total_disk_space&quot;, root.getTotalSpace());</span>
    +<span class="nc" id="L116">            metrics.put(&quot;free_disk_space&quot;, root.getFreeSpace());</span>
    +
    +            // Process info
    +<span class="nc" id="L119">            metrics.put(&quot;process_id&quot;, ProcessHandle.current().pid());</span>
    +
    +            // JVM metrics
    +<span class="nc" id="L122">            RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();</span>
    +<span class="nc" id="L123">            metrics.put(&quot;jvm_uptime_ms&quot;, runtimeMXBean.getUptime());</span>
    +<span class="nc" id="L124">            metrics.put(&quot;jvm_start_time&quot;, runtimeMXBean.getStartTime());</span>
    +
    +            // Memory metrics
    +<span class="nc" id="L127">            MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();</span>
    +<span class="nc" id="L128">            metrics.put(&quot;heap_memory_usage&quot;, memoryMXBean.getHeapMemoryUsage().getUsed());</span>
    +<span class="nc" id="L129">            metrics.put(&quot;non_heap_memory_usage&quot;, memoryMXBean.getNonHeapMemoryUsage().getUsed());</span>
    +
    +            // CPU metrics
    +<span class="nc" id="L132">            OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();</span>
    +<span class="nc" id="L133">            metrics.put(&quot;system_load_average&quot;, osMXBean.getSystemLoadAverage());</span>
    +
    +            // Thread metrics
    +<span class="nc" id="L136">            ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();</span>
    +<span class="nc" id="L137">            metrics.put(&quot;thread_count&quot;, threadMXBean.getThreadCount());</span>
    +<span class="nc" id="L138">            metrics.put(&quot;daemon_thread_count&quot;, threadMXBean.getDaemonThreadCount());</span>
    +<span class="nc" id="L139">            metrics.put(&quot;peak_thread_count&quot;, threadMXBean.getPeakThreadCount());</span>
    +
    +            // Garbage collection metrics
    +<span class="nc bnc" id="L142" title="All 2 branches missed.">            for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {</span>
    +<span class="nc" id="L143">                metrics.put(&quot;gc_&quot; + gcBean.getName() + &quot;_count&quot;, gcBean.getCollectionCount());</span>
    +<span class="nc" id="L144">                metrics.put(&quot;gc_&quot; + gcBean.getName() + &quot;_time&quot;, gcBean.getCollectionTime());</span>
    +<span class="nc" id="L145">            }</span>
    +
    +            // Network interfaces
    +<span class="nc" id="L148">            metrics.put(&quot;network_interfaces&quot;, getNetworkInterfacesInfo());</span>
    +
    +            // Docker detection and stats
    +<span class="nc" id="L151">            boolean isDocker = isRunningInDocker();</span>
    +<span class="nc bnc" id="L152" title="All 2 branches missed.">            if (isDocker) {</span>
    +<span class="nc" id="L153">                metrics.put(&quot;docker_metrics&quot;, getDockerMetrics());</span>
    +            }
    +<span class="nc" id="L155">            metrics.put(&quot;application_properties&quot;, captureApplicationProperties());</span>
    +
    +<span class="nc bnc" id="L157" title="All 2 branches missed.">            if (userService != null) {</span>
    +<span class="nc" id="L158">                metrics.put(&quot;total_users_created&quot;, userService.getTotalUsersCount());</span>
    +            }
    +
    +<span class="nc" id="L161">        } catch (Exception e) {</span>
    +<span class="nc" id="L162">            metrics.put(&quot;error&quot;, e.getMessage());</span>
    +<span class="nc" id="L163">        }</span>
    +
    +<span class="nc" id="L165">        return metrics;</span>
    +    }
    +
    +    private boolean isRunningInDocker() {
    +<span class="nc" id="L169">        return Files.exists(Paths.get(&quot;/.dockerenv&quot;));</span>
    +    }
    +
    +    private Map&lt;String, Object&gt; getDockerMetrics() {
    +<span class="nc" id="L173">        Map&lt;String, Object&gt; dockerMetrics = new HashMap&lt;&gt;();</span>
    +
    +        // Network-related Docker info
    +<span class="nc" id="L176">        dockerMetrics.put(&quot;docker_network_mode&quot;, System.getenv(&quot;DOCKER_NETWORK_MODE&quot;));</span>
    +
    +        // Container name (if set)
    +<span class="nc" id="L179">        String containerName = System.getenv(&quot;CONTAINER_NAME&quot;);</span>
    +<span class="nc bnc" id="L180" title="All 4 branches missed.">        if (containerName != null &amp;&amp; !containerName.isEmpty()) {</span>
    +<span class="nc" id="L181">            dockerMetrics.put(&quot;container_name&quot;, containerName);</span>
    +        }
    +
    +        // Docker compose information
    +<span class="nc" id="L185">        String composeProject = System.getenv(&quot;COMPOSE_PROJECT_NAME&quot;);</span>
    +<span class="nc" id="L186">        String composeService = System.getenv(&quot;COMPOSE_SERVICE_NAME&quot;);</span>
    +<span class="nc bnc" id="L187" title="All 4 branches missed.">        if (composeProject != null &amp;&amp; composeService != null) {</span>
    +<span class="nc" id="L188">            dockerMetrics.put(&quot;compose_project&quot;, composeProject);</span>
    +<span class="nc" id="L189">            dockerMetrics.put(&quot;compose_service&quot;, composeService);</span>
    +        }
    +
    +        // Kubernetes-specific info (if running in K8s)
    +<span class="nc" id="L193">        String k8sPodName = System.getenv(&quot;KUBERNETES_POD_NAME&quot;);</span>
    +<span class="nc bnc" id="L194" title="All 2 branches missed.">        if (k8sPodName != null) {</span>
    +<span class="nc" id="L195">            dockerMetrics.put(&quot;k8s_pod_name&quot;, k8sPodName);</span>
    +<span class="nc" id="L196">            dockerMetrics.put(&quot;k8s_namespace&quot;, System.getenv(&quot;KUBERNETES_NAMESPACE&quot;));</span>
    +<span class="nc" id="L197">            dockerMetrics.put(&quot;k8s_node_name&quot;, System.getenv(&quot;KUBERNETES_NODE_NAME&quot;));</span>
    +        }
    +
    +        // New environment variables
    +<span class="nc" id="L201">        dockerMetrics.put(&quot;version_tag&quot;, System.getenv(&quot;VERSION_TAG&quot;));</span>
    +<span class="nc" id="L202">        dockerMetrics.put(&quot;docker_enable_security&quot;, System.getenv(&quot;DOCKER_ENABLE_SECURITY&quot;));</span>
    +<span class="nc" id="L203">        dockerMetrics.put(&quot;fat_docker&quot;, System.getenv(&quot;FAT_DOCKER&quot;));</span>
    +
    +<span class="nc" id="L205">        return dockerMetrics;</span>
    +    }
    +
    +    private void addIfNotEmpty(Map&lt;String, Object&gt; map, String key, Object value) {
    +<span class="nc bnc" id="L209" title="All 2 branches missed.">        if (value != null) {</span>
    +<span class="nc bnc" id="L210" title="All 2 branches missed.">            if (value instanceof String strValue) {</span>
    +<span class="nc bnc" id="L211" title="All 2 branches missed.">                if (!StringUtils.isBlank(strValue)) {</span>
    +<span class="nc" id="L212">                    map.put(key, strValue.trim());</span>
    +                }
    +            } else {
    +<span class="nc" id="L215">                map.put(key, value);</span>
    +            }
    +        }
    +<span class="nc" id="L218">    }</span>
    +
    +    public Map&lt;String, Object&gt; captureApplicationProperties() {
    +<span class="nc" id="L221">        Map&lt;String, Object&gt; properties = new HashMap&lt;&gt;();</span>
    +
    +        // Capture Legal properties
    +<span class="nc" id="L224">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;legal_termsAndConditions&quot;,
    +<span class="nc" id="L227">                applicationProperties.getLegal().getTermsAndConditions());</span>
    +<span class="nc" id="L228">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;legal_privacyPolicy&quot;,
    +<span class="nc" id="L231">                applicationProperties.getLegal().getPrivacyPolicy());</span>
    +<span class="nc" id="L232">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;legal_accessibilityStatement&quot;,
    +<span class="nc" id="L235">                applicationProperties.getLegal().getAccessibilityStatement());</span>
    +<span class="nc" id="L236">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;legal_cookiePolicy&quot;,
    +<span class="nc" id="L239">                applicationProperties.getLegal().getCookiePolicy());</span>
    +<span class="nc" id="L240">        addIfNotEmpty(</span>
    +<span class="nc" id="L241">                properties, &quot;legal_impressum&quot;, applicationProperties.getLegal().getImpressum());</span>
    +
    +        // Capture Security properties
    +<span class="nc" id="L244">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;security_enableLogin&quot;,
    +<span class="nc" id="L247">                applicationProperties.getSecurity().getEnableLogin());</span>
    +<span class="nc" id="L248">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;security_csrfDisabled&quot;,
    +<span class="nc" id="L251">                applicationProperties.getSecurity().getCsrfDisabled());</span>
    +<span class="nc" id="L252">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;security_loginAttemptCount&quot;,
    +<span class="nc" id="L255">                applicationProperties.getSecurity().getLoginAttemptCount());</span>
    +<span class="nc" id="L256">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;security_loginResetTimeMinutes&quot;,
    +<span class="nc" id="L259">                applicationProperties.getSecurity().getLoginResetTimeMinutes());</span>
    +<span class="nc" id="L260">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;security_loginMethod&quot;,
    +<span class="nc" id="L263">                applicationProperties.getSecurity().getLoginMethod());</span>
    +
    +        // Capture OAuth2 properties (excluding sensitive information)
    +<span class="nc" id="L266">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;security_oauth2_enabled&quot;,
    +<span class="nc" id="L269">                applicationProperties.getSecurity().getOauth2().getEnabled());</span>
    +<span class="nc bnc" id="L270" title="All 2 branches missed.">        if (applicationProperties.getSecurity().getOauth2().getEnabled()) {</span>
    +<span class="nc" id="L271">            addIfNotEmpty(</span>
    +                    properties,
    +                    &quot;security_oauth2_autoCreateUser&quot;,
    +<span class="nc" id="L274">                    applicationProperties.getSecurity().getOauth2().getAutoCreateUser());</span>
    +<span class="nc" id="L275">            addIfNotEmpty(</span>
    +                    properties,
    +                    &quot;security_oauth2_blockRegistration&quot;,
    +<span class="nc" id="L278">                    applicationProperties.getSecurity().getOauth2().getBlockRegistration());</span>
    +<span class="nc" id="L279">            addIfNotEmpty(</span>
    +                    properties,
    +                    &quot;security_oauth2_useAsUsername&quot;,
    +<span class="nc" id="L282">                    applicationProperties.getSecurity().getOauth2().getUseAsUsername());</span>
    +<span class="nc" id="L283">            addIfNotEmpty(</span>
    +                    properties,
    +                    &quot;security_oauth2_provider&quot;,
    +<span class="nc" id="L286">                    applicationProperties.getSecurity().getOauth2().getProvider());</span>
    +        }
    +        // Capture System properties
    +<span class="nc" id="L289">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;system_defaultLocale&quot;,
    +<span class="nc" id="L292">                applicationProperties.getSystem().getDefaultLocale());</span>
    +<span class="nc" id="L293">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;system_googlevisibility&quot;,
    +<span class="nc" id="L296">                applicationProperties.getSystem().getGooglevisibility());</span>
    +<span class="nc" id="L297">        addIfNotEmpty(</span>
    +<span class="nc" id="L298">                properties, &quot;system_showUpdate&quot;, applicationProperties.getSystem().isShowUpdate());</span>
    +<span class="nc" id="L299">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;system_showUpdateOnlyAdmin&quot;,
    +<span class="nc" id="L302">                applicationProperties.getSystem().getShowUpdateOnlyAdmin());</span>
    +<span class="nc" id="L303">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;system_customHTMLFiles&quot;,
    +<span class="nc" id="L306">                applicationProperties.getSystem().isCustomHTMLFiles());</span>
    +<span class="nc" id="L307">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;system_tessdataDir&quot;,
    +<span class="nc" id="L310">                applicationProperties.getSystem().getTessdataDir());</span>
    +<span class="nc" id="L311">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;system_enableAlphaFunctionality&quot;,
    +<span class="nc" id="L314">                applicationProperties.getSystem().getEnableAlphaFunctionality());</span>
    +<span class="nc" id="L315">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;system_enableAnalytics&quot;,
    +<span class="nc" id="L318">                applicationProperties.getSystem().isAnalyticsEnabled());</span>
    +
    +        // Capture UI properties
    +<span class="nc" id="L321">        addIfNotEmpty(properties, &quot;ui_appName&quot;, applicationProperties.getUi().getAppName());</span>
    +<span class="nc" id="L322">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;ui_homeDescription&quot;,
    +<span class="nc" id="L325">                applicationProperties.getUi().getHomeDescription());</span>
    +<span class="nc" id="L326">        addIfNotEmpty(</span>
    +<span class="nc" id="L327">                properties, &quot;ui_appNameNavbar&quot;, applicationProperties.getUi().getAppNameNavbar());</span>
    +
    +        // Capture Metrics properties
    +<span class="nc" id="L330">        addIfNotEmpty(</span>
    +<span class="nc" id="L331">                properties, &quot;metrics_enabled&quot;, applicationProperties.getMetrics().getEnabled());</span>
    +
    +        // Capture EnterpriseEdition properties
    +<span class="nc" id="L334">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;enterpriseEdition_enabled&quot;,
    +<span class="nc" id="L337">                applicationProperties.getPremium().isEnabled());</span>
    +<span class="nc bnc" id="L338" title="All 2 branches missed.">        if (applicationProperties.getPremium().isEnabled()) {</span>
    +<span class="nc" id="L339">            addIfNotEmpty(</span>
    +                    properties,
    +                    &quot;enterpriseEdition_customMetadata_autoUpdateMetadata&quot;,
    +<span class="nc" id="L342">                    applicationProperties</span>
    +<span class="nc" id="L343">                            .getPremium()</span>
    +<span class="nc" id="L344">                            .getProFeatures()</span>
    +<span class="nc" id="L345">                            .getCustomMetadata()</span>
    +<span class="nc" id="L346">                            .isAutoUpdateMetadata());</span>
    +<span class="nc" id="L347">            addIfNotEmpty(</span>
    +                    properties,
    +                    &quot;enterpriseEdition_customMetadata_author&quot;,
    +                    applicationProperties
    +<span class="nc" id="L351">                            .getPremium()</span>
    +<span class="nc" id="L352">                            .getProFeatures()</span>
    +<span class="nc" id="L353">                            .getCustomMetadata()</span>
    +<span class="nc" id="L354">                            .getAuthor());</span>
    +<span class="nc" id="L355">            addIfNotEmpty(</span>
    +                    properties,
    +                    &quot;enterpriseEdition_customMetadata_creator&quot;,
    +                    applicationProperties
    +<span class="nc" id="L359">                            .getPremium()</span>
    +<span class="nc" id="L360">                            .getProFeatures()</span>
    +<span class="nc" id="L361">                            .getCustomMetadata()</span>
    +<span class="nc" id="L362">                            .getCreator());</span>
    +<span class="nc" id="L363">            addIfNotEmpty(</span>
    +                    properties,
    +                    &quot;enterpriseEdition_customMetadata_producer&quot;,
    +                    applicationProperties
    +<span class="nc" id="L367">                            .getPremium()</span>
    +<span class="nc" id="L368">                            .getProFeatures()</span>
    +<span class="nc" id="L369">                            .getCustomMetadata()</span>
    +<span class="nc" id="L370">                            .getProducer());</span>
    +        }
    +        // Capture AutoPipeline properties
    +<span class="nc" id="L373">        addIfNotEmpty(</span>
    +                properties,
    +                &quot;autoPipeline_outputFolder&quot;,
    +<span class="nc" id="L376">                applicationProperties.getAutoPipeline().getOutputFolder());</span>
    +
    +<span class="nc" id="L378">        return properties;</span>
    +    }
    +
    +    private String getMacAddress() {
    +        try {
    +            Enumeration&lt;NetworkInterface&gt; networkInterfaces =
    +<span class="nc" id="L384">                    NetworkInterface.getNetworkInterfaces();</span>
    +<span class="nc bnc" id="L385" title="All 2 branches missed.">            while (networkInterfaces.hasMoreElements()) {</span>
    +<span class="nc" id="L386">                NetworkInterface ni = networkInterfaces.nextElement();</span>
    +<span class="nc" id="L387">                byte[] hardwareAddress = ni.getHardwareAddress();</span>
    +<span class="nc bnc" id="L388" title="All 2 branches missed.">                if (hardwareAddress != null) {</span>
    +<span class="nc" id="L389">                    String[] hexadecimal = new String[hardwareAddress.length];</span>
    +<span class="nc bnc" id="L390" title="All 2 branches missed.">                    for (int i = 0; i &lt; hardwareAddress.length; i++) {</span>
    +<span class="nc" id="L391">                        hexadecimal[i] = String.format(&quot;%02X&quot;, hardwareAddress[i]);</span>
    +                    }
    +<span class="nc" id="L393">                    return String.join(&quot;-&quot;, hexadecimal);</span>
    +                }
    +<span class="nc" id="L395">            }</span>
    +<span class="nc" id="L396">        } catch (Exception e) {</span>
    +            // Handle exception
    +<span class="nc" id="L398">        }</span>
    +<span class="nc" id="L399">        return &quot;Unknown&quot;;</span>
    +    }
    +
    +    private Map&lt;String, String&gt; getNetworkInterfacesInfo() {
    +<span class="nc" id="L403">        Map&lt;String, String&gt; interfacesInfo = new HashMap&lt;&gt;();</span>
    +        try {
    +<span class="nc" id="L405">            Enumeration&lt;NetworkInterface&gt; nets = NetworkInterface.getNetworkInterfaces();</span>
    +<span class="nc bnc" id="L406" title="All 2 branches missed.">            while (nets.hasMoreElements()) {</span>
    +<span class="nc" id="L407">                NetworkInterface netint = nets.nextElement();</span>
    +<span class="nc" id="L408">                interfacesInfo.put(netint.getName(), netint.getDisplayName());</span>
    +<span class="nc" id="L409">            }</span>
    +<span class="nc" id="L410">        } catch (Exception e) {</span>
    +<span class="nc" id="L411">            interfacesInfo.put(&quot;error&quot;, e.getMessage());</span>
    +<span class="nc" id="L412">        }</span>
    +<span class="nc" id="L413">        return interfacesInfo;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/SignatureService.html b/testResult/html/stirling.software.SPDF.service/SignatureService.html
    new file mode 100644
    index 000000000..5a2e53411
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/SignatureService.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SignatureService</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_class">SignatureService</span></div><h1>SignatureService</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">240 of 240</td><td class="ctr2">0%</td><td class="bar">28 of 28</td><td class="ctr2">0%</td><td class="ctr1">24</td><td class="ctr2">24</td><td class="ctr1">44</td><td class="ctr2">44</td><td class="ctr1">10</td><td class="ctr2">10</td></tr></tfoot><tbody><tr><td id="a0"><a href="SignatureService.java.html#L40" class="el_method">getAvailableSignatures(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="65" alt="65"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h0">15</td><td class="ctr2" id="i0">15</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="SignatureService.java.html#L76" class="el_method">getSignatureBytes(String, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="52" alt="52"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h1">8</td><td class="ctr2" id="i1">8</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="SignatureService.java.html#L31" class="el_method">hasAccessToFile(String, String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="83" height="10" title="45" alt="45"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f4">3</td><td class="ctr2" id="g4">3</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="SignatureService.java.html#L93" class="el_method">isImageFile(Path)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="25" alt="25"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a9"><a href="SignatureService.java.html#L101" class="el_method">validateFileName(String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="18" alt="18"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="6" alt="6"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a2"><a href="SignatureService.java.html#L69" class="el_method">getSignaturesFromFolder(Path, String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="10" alt="10"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a7"><a href="SignatureService.java.html#L24" class="el_method">SignatureService()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="9" alt="9"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h5">4</td><td class="ctr2" id="i5">4</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a6"><a href="SignatureService.java.html#L71" class="el_method">lambda$getSignaturesFromFolder$1(String, Path)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="8" alt="8"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a5"><a href="SignatureService.java.html#L70" class="el_method">lambda$getSignaturesFromFolder$0(Path)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="4" alt="4"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a8"><a href="SignatureService.java.html#L20" class="el_method">static {...}</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="4" alt="4"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/SignatureService.java.html b/testResult/html/stirling.software.SPDF.service/SignatureService.java.html
    new file mode 100644
    index 000000000..b98f43942
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/SignatureService.java.html
    @@ -0,0 +1,106 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SignatureService.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.service</a> &gt; <span class="el_source">SignatureService.java</span></div><h1>SignatureService.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.service;
    +
    +import java.io.FileNotFoundException;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +import org.springframework.stereotype.Service;
    +import org.thymeleaf.util.StringUtils;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.model.SignatureFile;
    +
    +@Service
    +<span class="nc" id="L20">@Slf4j</span>
    +public class SignatureService {
    +
    +    private final String SIGNATURE_BASE_PATH;
    +<span class="nc" id="L24">    private final String ALL_USERS_FOLDER = &quot;ALL_USERS&quot;;</span>
    +
    +<span class="nc" id="L26">    public SignatureService() {</span>
    +<span class="nc" id="L27">        SIGNATURE_BASE_PATH = InstallationPathConfig.getSignaturesPath();</span>
    +<span class="nc" id="L28">    }</span>
    +
    +    public boolean hasAccessToFile(String username, String fileName) throws IOException {
    +<span class="nc" id="L31">        validateFileName(fileName);</span>
    +        // Check if file exists in user's personal folder or ALL_USERS folder
    +<span class="nc" id="L33">        Path userPath = Paths.get(SIGNATURE_BASE_PATH, username, fileName);</span>
    +<span class="nc" id="L34">        Path allUsersPath = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER, fileName);</span>
    +
    +<span class="nc bnc" id="L36" title="All 4 branches missed.">        return Files.exists(userPath) || Files.exists(allUsersPath);</span>
    +    }
    +
    +    public List&lt;SignatureFile&gt; getAvailableSignatures(String username) {
    +<span class="nc" id="L40">        List&lt;SignatureFile&gt; signatures = new ArrayList&lt;&gt;();</span>
    +
    +        // Get signatures from user's personal folder
    +<span class="nc bnc" id="L43" title="All 2 branches missed.">        if (!StringUtils.isEmptyOrWhitespace(username)) {</span>
    +<span class="nc" id="L44">            Path userFolder = Paths.get(SIGNATURE_BASE_PATH, username);</span>
    +<span class="nc bnc" id="L45" title="All 2 branches missed.">            if (Files.exists(userFolder)) {</span>
    +                try {
    +<span class="nc" id="L47">                    signatures.addAll(getSignaturesFromFolder(userFolder, &quot;Personal&quot;));</span>
    +<span class="nc" id="L48">                } catch (IOException e) {</span>
    +<span class="nc" id="L49">                    log.error(&quot;Error reading user signatures folder&quot;, e);</span>
    +<span class="nc" id="L50">                }</span>
    +            }
    +        }
    +
    +        // Get signatures from ALL_USERS folder
    +<span class="nc" id="L55">        Path allUsersFolder = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER);</span>
    +<span class="nc bnc" id="L56" title="All 2 branches missed.">        if (Files.exists(allUsersFolder)) {</span>
    +            try {
    +<span class="nc" id="L58">                signatures.addAll(getSignaturesFromFolder(allUsersFolder, &quot;Shared&quot;));</span>
    +<span class="nc" id="L59">            } catch (IOException e) {</span>
    +<span class="nc" id="L60">                log.error(&quot;Error reading shared signatures folder&quot;, e);</span>
    +<span class="nc" id="L61">            }</span>
    +        }
    +
    +<span class="nc" id="L64">        return signatures;</span>
    +    }
    +
    +    private List&lt;SignatureFile&gt; getSignaturesFromFolder(Path folder, String category)
    +            throws IOException {
    +<span class="nc" id="L69">        return Files.list(folder)</span>
    +<span class="nc" id="L70">                .filter(path -&gt; isImageFile(path))</span>
    +<span class="nc" id="L71">                .map(path -&gt; new SignatureFile(path.getFileName().toString(), category))</span>
    +<span class="nc" id="L72">                .toList();</span>
    +    }
    +
    +    public byte[] getSignatureBytes(String username, String fileName) throws IOException {
    +<span class="nc" id="L76">        validateFileName(fileName);</span>
    +        // First try user's personal folder
    +<span class="nc" id="L78">        Path userPath = Paths.get(SIGNATURE_BASE_PATH, username, fileName);</span>
    +<span class="nc bnc" id="L79" title="All 2 branches missed.">        if (Files.exists(userPath)) {</span>
    +<span class="nc" id="L80">            return Files.readAllBytes(userPath);</span>
    +        }
    +
    +        // Then try ALL_USERS folder
    +<span class="nc" id="L84">        Path allUsersPath = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER, fileName);</span>
    +<span class="nc bnc" id="L85" title="All 2 branches missed.">        if (Files.exists(allUsersPath)) {</span>
    +<span class="nc" id="L86">            return Files.readAllBytes(allUsersPath);</span>
    +        }
    +
    +<span class="nc" id="L89">        throw new FileNotFoundException(&quot;Signature file not found&quot;);</span>
    +    }
    +
    +    private boolean isImageFile(Path path) {
    +<span class="nc" id="L93">        String fileName = path.getFileName().toString().toLowerCase();</span>
    +<span class="nc bnc" id="L94" title="All 2 branches missed.">        return fileName.endsWith(&quot;.jpg&quot;)</span>
    +<span class="nc bnc" id="L95" title="All 2 branches missed.">                || fileName.endsWith(&quot;.jpeg&quot;)</span>
    +<span class="nc bnc" id="L96" title="All 2 branches missed.">                || fileName.endsWith(&quot;.png&quot;)</span>
    +<span class="nc bnc" id="L97" title="All 2 branches missed.">                || fileName.endsWith(&quot;.gif&quot;);</span>
    +    }
    +
    +    private void validateFileName(String fileName) {
    +<span class="nc bnc" id="L101" title="All 6 branches missed.">        if (fileName.contains(&quot;..&quot;) || fileName.contains(&quot;/&quot;) || fileName.contains(&quot;\\&quot;)) {</span>
    +<span class="nc" id="L102">            throw new IllegalArgumentException(&quot;Invalid filename&quot;);</span>
    +        }
    +<span class="nc" id="L104">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/index.html b/testResult/html/stirling.software.SPDF.service/index.html
    new file mode 100644
    index 000000000..50bb25b11
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.service</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.service</span></div><h1>stirling.software.SPDF.service</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2,624 of 2,633</td><td class="ctr2">0%</td><td class="bar">208 of 208</td><td class="ctr2">0%</td><td class="ctr1">195</td><td class="ctr2">196</td><td class="ctr1">631</td><td class="ctr2">633</td><td class="ctr1">91</td><td class="ctr2">92</td><td class="ctr1">7</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a6"><a href="PostHogService.html" class="el_class">PostHogService</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="873" alt="873"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="42" alt="42"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">31</td><td class="ctr2" id="g1">31</td><td class="ctr1" id="h0">210</td><td class="ctr2" id="i0">210</td><td class="ctr1" id="j1">10</td><td class="ctr2" id="k1">10</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a1"><a href="CustomPDFDocumentFactory.html" class="el_class">CustomPDFDocumentFactory</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="104" height="10" title="760" alt="760"/><img src="../jacoco-resources/greenbar.gif" width="1" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">1%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="52" alt="52"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">69</td><td class="ctr2" id="g0">70</td><td class="ctr1" id="h1">163</td><td class="ctr2" id="i1">165</td><td class="ctr1" id="j0">43</td><td class="ctr2" id="k0">44</td><td class="ctr1" id="l7">0</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a0"><a href="CertificateValidationService.html" class="el_class">CertificateValidationService</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="259" alt="259"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="55" height="10" title="24" alt="24"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">21</td><td class="ctr2" id="g3">21</td><td class="ctr1" id="h2">73</td><td class="ctr2" id="i2">73</td><td class="ctr1" id="j3">9</td><td class="ctr2" id="k3">9</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a7"><a href="SignatureService.html" class="el_class">SignatureService</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="240" alt="240"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="28" alt="28"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">24</td><td class="ctr2" id="g2">24</td><td class="ctr1" id="h4">44</td><td class="ctr2" id="i4">44</td><td class="ctr1" id="j2">10</td><td class="ctr2" id="k2">10</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a5"><a href="PdfMetadataService.html" class="el_class">PdfMetadataService</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="174" alt="174"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="14" alt="14"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f5">14</td><td class="ctr2" id="g5">14</td><td class="ctr1" id="h3">61</td><td class="ctr2" id="i3">61</td><td class="ctr1" id="j4">7</td><td class="ctr2" id="k4">7</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a3"><a href="MetricsAggregatorService.html" class="el_class">MetricsAggregatorService</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="169" alt="169"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="28" alt="28"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">18</td><td class="ctr2" id="g4">18</td><td class="ctr1" id="h5">43</td><td class="ctr2" id="i5">43</td><td class="ctr1" id="j6">4</td><td class="ctr2" id="k6">4</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a2"><a href="LanguageService.html" class="el_class">LanguageService</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="89" alt="89"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="12" alt="12"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">12</td><td class="ctr2" id="g6">12</td><td class="ctr1" id="h6">24</td><td class="ctr2" id="i6">24</td><td class="ctr1" id="j5">6</td><td class="ctr2" id="k5">6</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a4"><a href="PdfImageRemovalService.html" class="el_class">PdfImageRemovalService</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="60" alt="60"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="8" alt="8"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">6</td><td class="ctr2" id="g7">6</td><td class="ctr1" id="h7">13</td><td class="ctr2" id="i7">13</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.service/index.source.html b/testResult/html/stirling.software.SPDF.service/index.source.html
    new file mode 100644
    index 000000000..82d82512a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.service/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.service</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.service</span></div><h1>stirling.software.SPDF.service</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">2,624 of 2,633</td><td class="ctr2">0%</td><td class="bar">208 of 208</td><td class="ctr2">0%</td><td class="ctr1">195</td><td class="ctr2">196</td><td class="ctr1">631</td><td class="ctr2">633</td><td class="ctr1">91</td><td class="ctr2">92</td><td class="ctr1">7</td><td class="ctr2">8</td></tr></tfoot><tbody><tr><td id="a6"><a href="PostHogService.java.html" class="el_source">PostHogService.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="873" alt="873"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="96" height="10" title="42" alt="42"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">31</td><td class="ctr2" id="g1">31</td><td class="ctr1" id="h0">210</td><td class="ctr2" id="i0">210</td><td class="ctr1" id="j1">10</td><td class="ctr2" id="k1">10</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a1"><a href="CustomPDFDocumentFactory.java.html" class="el_source">CustomPDFDocumentFactory.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="104" height="10" title="760" alt="760"/><img src="../jacoco-resources/greenbar.gif" width="1" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">1%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="52" alt="52"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">69</td><td class="ctr2" id="g0">70</td><td class="ctr1" id="h1">163</td><td class="ctr2" id="i1">165</td><td class="ctr1" id="j0">43</td><td class="ctr2" id="k0">44</td><td class="ctr1" id="l7">0</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a0"><a href="CertificateValidationService.java.html" class="el_source">CertificateValidationService.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="35" height="10" title="259" alt="259"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="55" height="10" title="24" alt="24"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">21</td><td class="ctr2" id="g3">21</td><td class="ctr1" id="h2">73</td><td class="ctr2" id="i2">73</td><td class="ctr1" id="j3">9</td><td class="ctr2" id="k3">9</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a7"><a href="SignatureService.java.html" class="el_source">SignatureService.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="240" alt="240"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="28" alt="28"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">24</td><td class="ctr2" id="g2">24</td><td class="ctr1" id="h4">44</td><td class="ctr2" id="i4">44</td><td class="ctr1" id="j2">10</td><td class="ctr2" id="k2">10</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a5"><a href="PdfMetadataService.java.html" class="el_source">PdfMetadataService.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="174" alt="174"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="14" alt="14"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f5">14</td><td class="ctr2" id="g5">14</td><td class="ctr1" id="h3">61</td><td class="ctr2" id="i3">61</td><td class="ctr1" id="j4">7</td><td class="ctr2" id="k4">7</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a3"><a href="MetricsAggregatorService.java.html" class="el_source">MetricsAggregatorService.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="169" alt="169"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="28" alt="28"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f4">18</td><td class="ctr2" id="g4">18</td><td class="ctr1" id="h5">43</td><td class="ctr2" id="i5">43</td><td class="ctr1" id="j6">4</td><td class="ctr2" id="k6">4</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a2"><a href="LanguageService.java.html" class="el_source">LanguageService.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="89" alt="89"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="12" alt="12"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f6">12</td><td class="ctr2" id="g6">12</td><td class="ctr1" id="h6">24</td><td class="ctr2" id="i6">24</td><td class="ctr1" id="j5">6</td><td class="ctr2" id="k5">6</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a4"><a href="PdfImageRemovalService.java.html" class="el_source">PdfImageRemovalService.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="60" alt="60"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="8" alt="8"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f7">6</td><td class="ctr2" id="g7">6</td><td class="ctr1" id="h7">13</td><td class="ctr2" id="i7">13</td><td class="ctr1" id="j7">2</td><td class="ctr2" id="k7">2</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m7">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/CustomColorReplaceStrategy.html b/testResult/html/stirling.software.SPDF.utils.misc/CustomColorReplaceStrategy.html
    new file mode 100644
    index 000000000..404d3763b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/CustomColorReplaceStrategy.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomColorReplaceStrategy</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_class">CustomColorReplaceStrategy</span></div><h1>CustomColorReplaceStrategy</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">273 of 273</td><td class="ctr2">0%</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">75</td><td class="ctr2">75</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="CustomColorReplaceStrategy.java.html#L54" class="el_method">replace()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="217" alt="217"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">56</td><td class="ctr2" id="i0">56</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="CustomColorReplaceStrategy.java.html#L155" class="el_method">checkSupportedFontForCharacter(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="38" alt="38"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">13</td><td class="ctr2" id="i1">13</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="CustomColorReplaceStrategy.java.html#L43" class="el_method">CustomColorReplaceStrategy(MultipartFile, ReplaceAndInvert, String, String, HighContrastColorCombination)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="14" alt="14"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">5</td><td class="ctr2" id="i2">5</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="CustomColorReplaceStrategy.java.html#L30" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/CustomColorReplaceStrategy.java.html b/testResult/html/stirling.software.SPDF.utils.misc/CustomColorReplaceStrategy.java.html
    new file mode 100644
    index 000000000..e2736c78c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/CustomColorReplaceStrategy.java.html
    @@ -0,0 +1,171 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomColorReplaceStrategy.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_source">CustomColorReplaceStrategy.java</span></div><h1>CustomColorReplaceStrategy.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils.misc;
    +
    +import java.awt.*;
    +import java.io.ByteArrayInputStream;
    +import java.io.ByteArrayOutputStream;
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.util.List;
    +import java.util.Set;
    +
    +import org.apache.pdfbox.Loader;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.PDPageTree;
    +import org.apache.pdfbox.pdmodel.font.PDFont;
    +import org.apache.pdfbox.pdmodel.font.PDFontFactory;
    +import org.apache.pdfbox.pdmodel.font.PDType1Font;
    +import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
    +import org.apache.pdfbox.text.TextPosition;
    +import org.springframework.core.io.InputStreamResource;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
    +import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
    +
    +<span class="nc" id="L30">@Slf4j</span>
    +public class CustomColorReplaceStrategy extends ReplaceAndInvertColorStrategy {
    +
    +    private String textColor;
    +    private String backgroundColor;
    +    private HighContrastColorCombination highContrastColorCombination;
    +
    +    public CustomColorReplaceStrategy(
    +            MultipartFile file,
    +            ReplaceAndInvert replaceAndInvert,
    +            String textColor,
    +            String backgroundColor,
    +            HighContrastColorCombination highContrastColorCombination) {
    +<span class="nc" id="L43">        super(file, replaceAndInvert);</span>
    +<span class="nc" id="L44">        this.textColor = textColor;</span>
    +<span class="nc" id="L45">        this.backgroundColor = backgroundColor;</span>
    +<span class="nc" id="L46">        this.highContrastColorCombination = highContrastColorCombination;</span>
    +<span class="nc" id="L47">    }</span>
    +
    +    @Override
    +    public InputStreamResource replace() throws IOException {
    +
    +        // If ReplaceAndInvert is HighContrastColor option, then get the colors of text and
    +        // background from static
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">        if (replaceAndInvert == ReplaceAndInvert.HIGH_CONTRAST_COLOR) {</span>
    +<span class="nc" id="L55">            String[] colors =</span>
    +<span class="nc" id="L56">                    HighContrastColorReplaceDecider.getColors(</span>
    +                            replaceAndInvert, highContrastColorCombination);
    +<span class="nc" id="L58">            this.textColor = colors[0];</span>
    +<span class="nc" id="L59">            this.backgroundColor = colors[1];</span>
    +        }
    +
    +        // Create a temporary file, with the original filename from the multipart file
    +<span class="nc" id="L63">        File file = Files.createTempFile(&quot;temp&quot;, getFileInput().getOriginalFilename()).toFile();</span>
    +
    +        // Transfer the content of the multipart file to the file
    +<span class="nc" id="L66">        getFileInput().transferTo(file);</span>
    +
    +<span class="nc" id="L68">        try (PDDocument document = Loader.loadPDF(file)) {</span>
    +
    +<span class="nc" id="L70">            PDPageTree pages = document.getPages();</span>
    +
    +<span class="nc bnc" id="L72" title="All 2 branches missed.">            for (PDPage page : pages) {</span>
    +
    +<span class="nc" id="L74">                PdfTextStripperCustom pdfTextStripperCustom = new PdfTextStripperCustom();</span>
    +                // Get text positions
    +<span class="nc" id="L76">                List&lt;List&lt;TextPosition&gt;&gt; charactersByArticle =</span>
    +<span class="nc" id="L77">                        pdfTextStripperCustom.processPageCustom(page);</span>
    +
    +                // Begin a new content stream
    +<span class="nc" id="L80">                PDPageContentStream contentStream =</span>
    +                        new PDPageContentStream(
    +                                document, page, PDPageContentStream.AppendMode.APPEND, true, true);
    +
    +                // Set the new text color
    +<span class="nc" id="L85">                contentStream.setNonStrokingColor(Color.decode(this.textColor));</span>
    +
    +                // Draw the text with the new color
    +<span class="nc bnc" id="L88" title="All 2 branches missed.">                for (List&lt;TextPosition&gt; textPositions : charactersByArticle) {</span>
    +<span class="nc bnc" id="L89" title="All 2 branches missed.">                    for (TextPosition text : textPositions) {</span>
    +                        // Move to the text position
    +<span class="nc" id="L91">                        contentStream.beginText();</span>
    +<span class="nc" id="L92">                        contentStream.newLineAtOffset(</span>
    +<span class="nc" id="L93">                                text.getX(), page.getMediaBox().getHeight() - text.getY());</span>
    +<span class="nc" id="L94">                        PDFont font = null;</span>
    +<span class="nc" id="L95">                        String unicodeText = text.getUnicode();</span>
    +                        try {
    +<span class="nc" id="L97">                            font = PDFontFactory.createFont(text.getFont().getCOSObject());</span>
    +<span class="nc" id="L98">                        } catch (IOException io) {</span>
    +<span class="nc" id="L99">                            log.info(&quot;Primary font not found, using fallback font.&quot;);</span>
    +<span class="nc" id="L100">                            font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);</span>
    +<span class="nc" id="L101">                        }</span>
    +                        // if a character is not supported by font, then look for supported font
    +                        try {
    +<span class="nc" id="L104">                            byte[] bytes = font.encode(unicodeText);</span>
    +<span class="nc" id="L105">                        } catch (IOException io) {</span>
    +<span class="nc" id="L106">                            log.info(&quot;text could not be encoded &quot;);</span>
    +<span class="nc" id="L107">                            font = checkSupportedFontForCharacter(unicodeText);</span>
    +<span class="nc" id="L108">                        } catch (IllegalArgumentException ie) {</span>
    +<span class="nc" id="L109">                            log.info(&quot;text not supported by font &quot;);</span>
    +<span class="nc" id="L110">                            font = checkSupportedFontForCharacter(unicodeText);</span>
    +                        } finally {
    +                            // if any other font is not supported, then replace default character *
    +<span class="nc bnc" id="L113" title="All 2 branches missed.">                            if (font == null) {</span>
    +<span class="nc" id="L114">                                font = new PDType1Font(Standard14Fonts.FontName.HELVETICA);</span>
    +<span class="nc" id="L115">                                unicodeText = &quot;*&quot;;</span>
    +                            }
    +                        }
    +<span class="nc" id="L118">                        contentStream.setFont(font, text.getFontSize());</span>
    +<span class="nc" id="L119">                        contentStream.showText(unicodeText);</span>
    +<span class="nc" id="L120">                        contentStream.endText();</span>
    +<span class="nc" id="L121">                    }</span>
    +<span class="nc" id="L122">                }</span>
    +                // Close the content stream
    +<span class="nc" id="L124">                contentStream.close();</span>
    +                // Use a content stream to overlay the background color
    +<span class="nc" id="L126">                try (PDPageContentStream contentStreamBg =</span>
    +                        new PDPageContentStream(
    +                                document,
    +                                page,
    +                                PDPageContentStream.AppendMode.PREPEND,
    +                                true,
    +                                true)) {
    +                    // Set background color (e.g., light yellow)
    +<span class="nc" id="L134">                    contentStreamBg.setNonStrokingColor(Color.decode(this.backgroundColor));</span>
    +<span class="nc" id="L135">                    contentStreamBg.addRect(</span>
    +<span class="nc" id="L136">                            0, 0, page.getMediaBox().getWidth(), page.getMediaBox().getHeight());</span>
    +<span class="nc" id="L137">                    contentStreamBg.fill();</span>
    +                }
    +<span class="nc" id="L139">            }</span>
    +            // Save the modified PDF to a ByteArrayOutputStream
    +<span class="nc" id="L141">            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L142">            document.save(byteArrayOutputStream);</span>
    +<span class="nc" id="L143">            document.close();</span>
    +
    +            // Prepare the modified PDF for download
    +<span class="nc" id="L146">            ByteArrayInputStream inputStream =</span>
    +<span class="nc" id="L147">                    new ByteArrayInputStream(byteArrayOutputStream.toByteArray());</span>
    +<span class="nc" id="L148">            InputStreamResource resource = new InputStreamResource(inputStream);</span>
    +<span class="nc" id="L149">            return resource;</span>
    +        }
    +    }
    +
    +    private PDFont checkSupportedFontForCharacter(String unicodeText) {
    +
    +<span class="nc" id="L155">        Set&lt;String&gt; fonts = Standard14Fonts.getNames();</span>
    +<span class="nc bnc" id="L156" title="All 2 branches missed.">        for (String font : fonts) {</span>
    +<span class="nc" id="L157">            Standard14Fonts.FontName fontName = Standard14Fonts.getMappedFontName(font);</span>
    +<span class="nc" id="L158">            PDFont currentFont = new PDType1Font(fontName);</span>
    +            try {
    +<span class="nc" id="L160">                byte[] bytes = currentFont.encode(unicodeText);</span>
    +<span class="nc" id="L161">                return currentFont;</span>
    +<span class="nc" id="L162">            } catch (IOException io) {</span>
    +<span class="nc" id="L163">                log.info(&quot;text could not be encoded &quot;);</span>
    +<span class="nc" id="L164">            } catch (IllegalArgumentException ie) {</span>
    +<span class="nc" id="L165">                log.info(&quot;text not supported by font &quot;);</span>
    +<span class="nc" id="L166">            }</span>
    +<span class="nc" id="L167">        }</span>
    +<span class="nc" id="L168">        return null;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/HighContrastColorReplaceDecider.html b/testResult/html/stirling.software.SPDF.utils.misc/HighContrastColorReplaceDecider.html
    new file mode 100644
    index 000000000..9ea238a6e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/HighContrastColorReplaceDecider.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>HighContrastColorReplaceDecider</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_class">HighContrastColorReplaceDecider</span></div><h1>HighContrastColorReplaceDecider</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">61 of 61</td><td class="ctr2">0%</td><td class="bar">8 of 8</td><td class="ctr2">0%</td><td class="ctr1">6</td><td class="ctr2">6</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="HighContrastColorReplaceDecider.java.html#L14" class="el_method">getColors(ReplaceAndInvert, HighContrastColorCombination)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="58" alt="58"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">9</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="HighContrastColorReplaceDecider.java.html#L6" class="el_method">HighContrastColorReplaceDecider()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/HighContrastColorReplaceDecider.java.html b/testResult/html/stirling.software.SPDF.utils.misc/HighContrastColorReplaceDecider.java.html
    new file mode 100644
    index 000000000..c5c3cf0b2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/HighContrastColorReplaceDecider.java.html
    @@ -0,0 +1,31 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>HighContrastColorReplaceDecider.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_source">HighContrastColorReplaceDecider.java</span></div><h1>HighContrastColorReplaceDecider.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils.misc;
    +
    +import stirling.software.SPDF.model.api.misc.HighContrastColorCombination;
    +import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
    +
    +<span class="nc" id="L6">public class HighContrastColorReplaceDecider {</span>
    +
    +    // To decide the text and background colors for High contrast color option for replace-invert
    +    // color feature
    +    public static String[] getColors(
    +            ReplaceAndInvert replaceAndInvert,
    +            HighContrastColorCombination highContrastColorCombination) {
    +
    +<span class="nc bnc" id="L14" title="All 2 branches missed.">        if (highContrastColorCombination == HighContrastColorCombination.BLACK_TEXT_ON_WHITE) {</span>
    +<span class="nc" id="L15">            return new String[] {&quot;0&quot;, &quot;16777215&quot;};</span>
    +<span class="nc bnc" id="L16" title="All 2 branches missed.">        } else if (highContrastColorCombination</span>
    +                == HighContrastColorCombination.GREEN_TEXT_ON_BLACK) {
    +<span class="nc" id="L18">            return new String[] {&quot;65280&quot;, &quot;0&quot;};</span>
    +<span class="nc bnc" id="L19" title="All 2 branches missed.">        } else if (highContrastColorCombination</span>
    +                == HighContrastColorCombination.WHITE_TEXT_ON_BLACK) {
    +<span class="nc" id="L21">            return new String[] {&quot;16777215&quot;, &quot;0&quot;};</span>
    +<span class="nc bnc" id="L22" title="All 2 branches missed.">        } else if (highContrastColorCombination</span>
    +                == HighContrastColorCombination.YELLOW_TEXT_ON_BLACK) {
    +
    +<span class="nc" id="L25">            return new String[] {&quot;16776960&quot;, &quot;0&quot;};</span>
    +        }
    +
    +<span class="nc" id="L28">        return null;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/InvertFullColorStrategy.html b/testResult/html/stirling.software.SPDF.utils.misc/InvertFullColorStrategy.html
    new file mode 100644
    index 000000000..952610cc2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/InvertFullColorStrategy.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InvertFullColorStrategy</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_class">InvertFullColorStrategy</span></div><h1>InvertFullColorStrategy</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">184 of 184</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">47</td><td class="ctr2">47</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a3"><a href="InvertFullColorStrategy.java.html#L33" class="el_method">replace()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="114" alt="114"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">30</td><td class="ctr2" id="i0">30</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="InvertFullColorStrategy.java.html#L100" class="el_method">invertImageColors(BufferedImage)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="54" alt="54"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">12</td><td class="ctr2" id="i1">12</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="InvertFullColorStrategy.java.html#L118" class="el_method">convertToBufferedImageTpFile(BufferedImage)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="11" alt="11"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="InvertFullColorStrategy.java.html#L27" class="el_method">InvertFullColorStrategy(MultipartFile, ReplaceAndInvert)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="5" alt="5"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/InvertFullColorStrategy.java.html b/testResult/html/stirling.software.SPDF.utils.misc/InvertFullColorStrategy.java.html
    new file mode 100644
    index 000000000..3f1bf9d75
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/InvertFullColorStrategy.java.html
    @@ -0,0 +1,123 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>InvertFullColorStrategy.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_source">InvertFullColorStrategy.java</span></div><h1>InvertFullColorStrategy.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils.misc;
    +
    +import java.awt.*;
    +import java.awt.image.BufferedImage;
    +import java.io.ByteArrayInputStream;
    +import java.io.ByteArrayOutputStream;
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +
    +import javax.imageio.ImageIO;
    +
    +import org.apache.pdfbox.Loader;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.apache.pdfbox.rendering.PDFRenderer;
    +import org.springframework.core.io.InputStreamResource;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
    +
    +public class InvertFullColorStrategy extends ReplaceAndInvertColorStrategy {
    +
    +    public InvertFullColorStrategy(MultipartFile file, ReplaceAndInvert replaceAndInvert) {
    +<span class="nc" id="L27">        super(file, replaceAndInvert);</span>
    +<span class="nc" id="L28">    }</span>
    +
    +    @Override
    +    public InputStreamResource replace() throws IOException {
    +
    +<span class="nc" id="L33">        File file = null;</span>
    +        try {
    +            // Create a temporary file, with the original filename from the multipart file
    +<span class="nc" id="L36">            file = Files.createTempFile(&quot;temp&quot;, getFileInput().getOriginalFilename()).toFile();</span>
    +
    +            // Transfer the content of the multipart file to the file
    +<span class="nc" id="L39">            getFileInput().transferTo(file);</span>
    +
    +            // Load the uploaded PDF
    +<span class="nc" id="L42">            PDDocument document = Loader.loadPDF(file);</span>
    +
    +            // Render each page and invert colors
    +<span class="nc" id="L45">            PDFRenderer pdfRenderer = new PDFRenderer(document);</span>
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">            for (int page = 0; page &lt; document.getNumberOfPages(); page++) {</span>
    +<span class="nc" id="L47">                BufferedImage image =</span>
    +<span class="nc" id="L48">                        pdfRenderer.renderImageWithDPI(page, 300); // Render page at 300 DPI</span>
    +
    +                // Invert the colors
    +<span class="nc" id="L51">                invertImageColors(image);</span>
    +
    +                // Create a new PDPage from the inverted image
    +<span class="nc" id="L54">                PDPage pdPage = document.getPage(page);</span>
    +<span class="nc" id="L55">                File tempImageFile = null;</span>
    +                try {
    +<span class="nc" id="L57">                    tempImageFile = convertToBufferedImageTpFile(image);</span>
    +<span class="nc" id="L58">                    PDImageXObject pdImage =</span>
    +<span class="nc" id="L59">                            PDImageXObject.createFromFileByContent(tempImageFile, document);</span>
    +
    +<span class="nc" id="L61">                    PDPageContentStream contentStream =</span>
    +                            new PDPageContentStream(
    +                                    document,
    +                                    pdPage,
    +                                    PDPageContentStream.AppendMode.OVERWRITE,
    +                                    true);
    +<span class="nc" id="L67">                    contentStream.drawImage(</span>
    +                            pdImage,
    +                            0,
    +                            0,
    +<span class="nc" id="L71">                            pdPage.getMediaBox().getWidth(),</span>
    +<span class="nc" id="L72">                            pdPage.getMediaBox().getHeight());</span>
    +<span class="nc" id="L73">                    contentStream.close();</span>
    +                } finally {
    +<span class="nc bnc" id="L75" title="All 4 branches missed.">                    if (tempImageFile != null &amp;&amp; tempImageFile.exists()) {</span>
    +<span class="nc" id="L76">                        Files.delete(tempImageFile.toPath());</span>
    +                    }
    +                }
    +            }
    +
    +            // Save the modified PDF to a ByteArrayOutputStream
    +<span class="nc" id="L82">            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L83">            document.save(byteArrayOutputStream);</span>
    +<span class="nc" id="L84">            document.close();</span>
    +
    +            // Prepare the modified PDF for download
    +<span class="nc" id="L87">            ByteArrayInputStream inputStream =</span>
    +<span class="nc" id="L88">                    new ByteArrayInputStream(byteArrayOutputStream.toByteArray());</span>
    +<span class="nc" id="L89">            InputStreamResource resource = new InputStreamResource(inputStream);</span>
    +<span class="nc" id="L90">            return resource;</span>
    +        } finally {
    +<span class="nc bnc" id="L92" title="All 4 branches missed.">            if (file != null &amp;&amp; file.exists()) {</span>
    +<span class="nc" id="L93">                Files.delete(file.toPath());</span>
    +            }
    +        }
    +    }
    +
    +    // Method to invert image colors
    +    private void invertImageColors(BufferedImage image) {
    +<span class="nc" id="L100">        int width = image.getWidth();</span>
    +<span class="nc" id="L101">        int height = image.getHeight();</span>
    +<span class="nc bnc" id="L102" title="All 2 branches missed.">        for (int x = 0; x &lt; width; x++) {</span>
    +<span class="nc bnc" id="L103" title="All 2 branches missed.">            for (int y = 0; y &lt; height; y++) {</span>
    +<span class="nc" id="L104">                int rgba = image.getRGB(x, y);</span>
    +<span class="nc" id="L105">                Color color = new Color(rgba, true);</span>
    +<span class="nc" id="L106">                Color invertedColor =</span>
    +                        new Color(
    +<span class="nc" id="L108">                                255 - color.getRed(),</span>
    +<span class="nc" id="L109">                                255 - color.getGreen(),</span>
    +<span class="nc" id="L110">                                255 - color.getBlue());</span>
    +<span class="nc" id="L111">                image.setRGB(x, y, invertedColor.getRGB());</span>
    +            }
    +        }
    +<span class="nc" id="L114">    }</span>
    +
    +    // Helper method to convert BufferedImage to InputStream
    +    private File convertToBufferedImageTpFile(BufferedImage image) throws IOException {
    +<span class="nc" id="L118">        File file = File.createTempFile(&quot;image&quot;, &quot;.png&quot;);</span>
    +<span class="nc" id="L119">        ImageIO.write(image, &quot;png&quot;, file);</span>
    +<span class="nc" id="L120">        return file;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/PdfTextStripperCustom.html b/testResult/html/stirling.software.SPDF.utils.misc/PdfTextStripperCustom.html
    new file mode 100644
    index 000000000..3ebff2b15
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/PdfTextStripperCustom.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfTextStripperCustom</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_class">PdfTextStripperCustom</span></div><h1>PdfTextStripperCustom</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">29 of 29</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="PdfTextStripperCustom.java.html#L23" class="el_method">processPageCustom(PDPage)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="26" alt="26"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PdfTextStripperCustom.java.html#L18" class="el_method">PdfTextStripperCustom()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/PdfTextStripperCustom.java.html b/testResult/html/stirling.software.SPDF.utils.misc/PdfTextStripperCustom.java.html
    new file mode 100644
    index 000000000..629d5193c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/PdfTextStripperCustom.java.html
    @@ -0,0 +1,37 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfTextStripperCustom.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_source">PdfTextStripperCustom.java</span></div><h1>PdfTextStripperCustom.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils.misc;
    +
    +import java.awt.geom.Rectangle2D;
    +import java.io.IOException;
    +import java.util.List;
    +
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.text.PDFTextStripperByArea;
    +import org.apache.pdfbox.text.TextPosition;
    +
    +public class PdfTextStripperCustom extends PDFTextStripperByArea {
    +
    +    /**
    +     * Constructor.
    +     *
    +     * @throws IOException If there is an error loading properties.
    +     */
    +<span class="nc" id="L18">    public PdfTextStripperCustom() throws IOException {}</span>
    +
    +    // To process the page text using stripper and returns the TextPosition and its values
    +    public List&lt;List&lt;TextPosition&gt;&gt; processPageCustom(PDPage page) throws IOException {
    +
    +<span class="nc" id="L23">        addRegion(</span>
    +                &quot;wholePage&quot;,
    +                new Rectangle2D.Float(
    +<span class="nc" id="L26">                        page.getMediaBox().getLowerLeftX(),</span>
    +<span class="nc" id="L27">                        page.getMediaBox().getLowerLeftY(),</span>
    +<span class="nc" id="L28">                        page.getMediaBox().getWidth(),</span>
    +<span class="nc" id="L29">                        page.getMediaBox().getHeight()));</span>
    +<span class="nc" id="L30">        extractRegions(page);</span>
    +
    +<span class="nc" id="L32">        List&lt;List&lt;TextPosition&gt;&gt; textPositions = getCharactersByArticle();</span>
    +
    +<span class="nc" id="L34">        return textPositions;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/ReplaceAndInvertColorStrategy.html b/testResult/html/stirling.software.SPDF.utils.misc/ReplaceAndInvertColorStrategy.html
    new file mode 100644
    index 000000000..8a15690a5
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/ReplaceAndInvertColorStrategy.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvertColorStrategy</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_class">ReplaceAndInvertColorStrategy</span></div><h1>ReplaceAndInvertColorStrategy</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">9 of 9</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ReplaceAndInvertColorStrategy.java.html#L20" class="el_method">ReplaceAndInvertColorStrategy(MultipartFile, ReplaceAndInvert)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/ReplaceAndInvertColorStrategy.java.html b/testResult/html/stirling.software.SPDF.utils.misc/ReplaceAndInvertColorStrategy.java.html
    new file mode 100644
    index 000000000..a09020ebb
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/ReplaceAndInvertColorStrategy.java.html
    @@ -0,0 +1,27 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ReplaceAndInvertColorStrategy.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils.misc</a> &gt; <span class="el_source">ReplaceAndInvertColorStrategy.java</span></div><h1>ReplaceAndInvertColorStrategy.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils.misc;
    +
    +import java.io.IOException;
    +
    +import org.springframework.core.io.InputStreamResource;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import lombok.Data;
    +import lombok.EqualsAndHashCode;
    +
    +import stirling.software.SPDF.model.api.PDFFile;
    +import stirling.software.SPDF.model.api.misc.ReplaceAndInvert;
    +
    +@Data
    +@EqualsAndHashCode(callSuper = true)
    +public abstract class ReplaceAndInvertColorStrategy extends PDFFile {
    +
    +    protected ReplaceAndInvert replaceAndInvert;
    +
    +<span class="nc" id="L20">    public ReplaceAndInvertColorStrategy(MultipartFile file, ReplaceAndInvert replaceAndInvert) {</span>
    +<span class="nc" id="L21">        setFileInput(file);</span>
    +<span class="nc" id="L22">        setReplaceAndInvert(replaceAndInvert);</span>
    +<span class="nc" id="L23">    }</span>
    +
    +    public abstract InputStreamResource replace() throws IOException;
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/index.html b/testResult/html/stirling.software.SPDF.utils.misc/index.html
    new file mode 100644
    index 000000000..ac2a43131
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.utils.misc</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.utils.misc</span></div><h1>stirling.software.SPDF.utils.misc</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">556 of 556</td><td class="ctr2">0%</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="ctr1">30</td><td class="ctr2">30</td><td class="ctr1">145</td><td class="ctr2">145</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="CustomColorReplaceStrategy.html" class="el_class">CustomColorReplaceStrategy</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="273" alt="273"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="102" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">10</td><td class="ctr2" id="g1">10</td><td class="ctr1" id="h0">75</td><td class="ctr2" id="i0">75</td><td class="ctr1" id="j0">4</td><td class="ctr2" id="k0">4</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a2"><a href="InvertFullColorStrategy.html" class="el_class">InvertFullColorStrategy</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="184" alt="184"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h1">47</td><td class="ctr2" id="i1">47</td><td class="ctr1" id="j1">4</td><td class="ctr2" id="k1">4</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a1"><a href="HighContrastColorReplaceDecider.html" class="el_class">HighContrastColorReplaceDecider</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="61" alt="61"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">6</td><td class="ctr2" id="g2">6</td><td class="ctr1" id="h2">10</td><td class="ctr2" id="i2">10</td><td class="ctr1" id="j2">2</td><td class="ctr2" id="k2">2</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="PdfTextStripperCustom.html" class="el_class">PdfTextStripperCustom</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="29" alt="29"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">9</td><td class="ctr2" id="i3">9</td><td class="ctr1" id="j3">2</td><td class="ctr2" id="k3">2</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a4"><a href="ReplaceAndInvertColorStrategy.html" class="el_class">ReplaceAndInvertColorStrategy</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="9" alt="9"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.misc/index.source.html b/testResult/html/stirling.software.SPDF.utils.misc/index.source.html
    new file mode 100644
    index 000000000..611fd86b8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.misc/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.utils.misc</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.utils.misc</span></div><h1>stirling.software.SPDF.utils.misc</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">556 of 556</td><td class="ctr2">0%</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="ctr1">30</td><td class="ctr2">30</td><td class="ctr1">145</td><td class="ctr2">145</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="CustomColorReplaceStrategy.java.html" class="el_source">CustomColorReplaceStrategy.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="273" alt="273"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="102" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">10</td><td class="ctr2" id="g1">10</td><td class="ctr1" id="h0">75</td><td class="ctr2" id="i0">75</td><td class="ctr1" id="j0">4</td><td class="ctr2" id="k0">4</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a2"><a href="InvertFullColorStrategy.java.html" class="el_source">InvertFullColorStrategy.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="184" alt="184"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="14" alt="14"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h1">47</td><td class="ctr2" id="i1">47</td><td class="ctr1" id="j1">4</td><td class="ctr2" id="k1">4</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a1"><a href="HighContrastColorReplaceDecider.java.html" class="el_source">HighContrastColorReplaceDecider.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="61" alt="61"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="68" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">6</td><td class="ctr2" id="g2">6</td><td class="ctr1" id="h2">10</td><td class="ctr2" id="i2">10</td><td class="ctr1" id="j2">2</td><td class="ctr2" id="k2">2</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a3"><a href="PdfTextStripperCustom.java.html" class="el_source">PdfTextStripperCustom.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="29" alt="29"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">9</td><td class="ctr2" id="i3">9</td><td class="ctr1" id="j3">2</td><td class="ctr2" id="k3">2</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a4"><a href="ReplaceAndInvertColorStrategy.java.html" class="el_source">ReplaceAndInvertColorStrategy.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="9" alt="9"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor$1.html b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor$1.html
    new file mode 100644
    index 000000000..a29e9d34c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StringToArrayListPropertyEditor.new TypeReference() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.propertyeditor</a> &gt; <span class="el_class">StringToArrayListPropertyEditor.new TypeReference() {...}</span></div><h1>StringToArrayListPropertyEditor.new TypeReference() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="StringToArrayListPropertyEditor.java.html#L29" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor.html b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor.html
    new file mode 100644
    index 000000000..e6abfe36b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StringToArrayListPropertyEditor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.propertyeditor</a> &gt; <span class="el_class">StringToArrayListPropertyEditor</span></div><h1>StringToArrayListPropertyEditor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">57 of 57</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a0"><a href="StringToArrayListPropertyEditor.java.html#L22" class="el_method">setAsText(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="45" alt="45"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="StringToArrayListPropertyEditor.java.html#L16" class="el_method">StringToArrayListPropertyEditor()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="21" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="StringToArrayListPropertyEditor.java.html#L15" class="el_method">static {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor.java.html b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor.java.html
    new file mode 100644
    index 000000000..31d584d08
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToArrayListPropertyEditor.java.html
    @@ -0,0 +1,39 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StringToArrayListPropertyEditor.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils.propertyeditor</a> &gt; <span class="el_source">StringToArrayListPropertyEditor.java</span></div><h1>StringToArrayListPropertyEditor.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils.propertyeditor;
    +
    +import java.beans.PropertyEditorSupport;
    +import java.util.ArrayList;
    +import java.util.List;
    +
    +import com.fasterxml.jackson.core.type.TypeReference;
    +import com.fasterxml.jackson.databind.DeserializationFeature;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.api.security.RedactionArea;
    +
    +<span class="nc" id="L15">@Slf4j</span>
    +<span class="nc" id="L16">public class StringToArrayListPropertyEditor extends PropertyEditorSupport {</span>
    +
    +<span class="nc" id="L18">    private final ObjectMapper objectMapper = new ObjectMapper();</span>
    +
    +    @Override
    +    public void setAsText(String text) throws IllegalArgumentException {
    +<span class="nc bnc" id="L22" title="All 4 branches missed.">        if (text == null || text.trim().isEmpty()) {</span>
    +<span class="nc" id="L23">            setValue(new ArrayList&lt;&gt;());</span>
    +<span class="nc" id="L24">            return;</span>
    +        }
    +        try {
    +<span class="nc" id="L27">            objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);</span>
    +<span class="nc" id="L28">            TypeReference&lt;ArrayList&lt;RedactionArea&gt;&gt; typeRef =</span>
    +<span class="nc" id="L29">                    new TypeReference&lt;ArrayList&lt;RedactionArea&gt;&gt;() {};</span>
    +<span class="nc" id="L30">            List&lt;RedactionArea&gt; list = objectMapper.readValue(text, typeRef);</span>
    +<span class="nc" id="L31">            setValue(list);</span>
    +<span class="nc" id="L32">        } catch (Exception e) {</span>
    +<span class="nc" id="L33">            log.error(&quot;Exception while converting {}&quot;, e);</span>
    +<span class="nc" id="L34">            throw new IllegalArgumentException(</span>
    +                    &quot;Failed to convert java.lang.String to java.util.List&quot;);
    +<span class="nc" id="L36">        }</span>
    +<span class="nc" id="L37">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor$1.html b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor$1.html
    new file mode 100644
    index 000000000..85cf4da39
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StringToMapPropertyEditor.new TypeReference() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.propertyeditor</a> &gt; <span class="el_class">StringToMapPropertyEditor.new TypeReference() {...}</span></div><h1>StringToMapPropertyEditor.new TypeReference() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="StringToMapPropertyEditor.java.html#L18" class="el_method">{...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor.html b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor.html
    new file mode 100644
    index 000000000..7732d912c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StringToMapPropertyEditor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.propertyeditor</a> &gt; <span class="el_class">StringToMapPropertyEditor</span></div><h1>StringToMapPropertyEditor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">31 of 31</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">2</td><td class="ctr2">2</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a0"><a href="StringToMapPropertyEditor.java.html#L17" class="el_method">setAsText(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="23" alt="23"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="StringToMapPropertyEditor.java.html#L10" class="el_method">StringToMapPropertyEditor()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="8" alt="8"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor.java.html b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor.java.html
    new file mode 100644
    index 000000000..d41575efe
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.propertyeditor/StringToMapPropertyEditor.java.html
    @@ -0,0 +1,27 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>StringToMapPropertyEditor.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils.propertyeditor</a> &gt; <span class="el_source">StringToMapPropertyEditor.java</span></div><h1>StringToMapPropertyEditor.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils.propertyeditor;
    +
    +import java.beans.PropertyEditorSupport;
    +import java.util.HashMap;
    +import java.util.Map;
    +
    +import com.fasterxml.jackson.core.type.TypeReference;
    +import com.fasterxml.jackson.databind.ObjectMapper;
    +
    +<span class="nc" id="L10">public class StringToMapPropertyEditor extends PropertyEditorSupport {</span>
    +
    +<span class="nc" id="L12">    private final ObjectMapper objectMapper = new ObjectMapper();</span>
    +
    +    @Override
    +    public void setAsText(String text) throws IllegalArgumentException {
    +        try {
    +<span class="nc" id="L17">            TypeReference&lt;HashMap&lt;String, String&gt;&gt; typeRef =</span>
    +<span class="nc" id="L18">                    new TypeReference&lt;HashMap&lt;String, String&gt;&gt;() {};</span>
    +<span class="nc" id="L19">            Map&lt;String, String&gt; map = objectMapper.readValue(text, typeRef);</span>
    +<span class="nc" id="L20">            setValue(map);</span>
    +<span class="nc" id="L21">        } catch (Exception e) {</span>
    +<span class="nc" id="L22">            throw new IllegalArgumentException(</span>
    +                    &quot;Failed to convert java.lang.String to java.util.Map&quot;);
    +<span class="nc" id="L24">        }</span>
    +<span class="nc" id="L25">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.propertyeditor/index.html b/testResult/html/stirling.software.SPDF.utils.propertyeditor/index.html
    new file mode 100644
    index 000000000..7ee9d0eb5
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.propertyeditor/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.utils.propertyeditor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.utils.propertyeditor</span></div><h1>stirling.software.SPDF.utils.propertyeditor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">100 of 100</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">26</td><td class="ctr2">26</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a0"><a href="StringToArrayListPropertyEditor.html" class="el_class">StringToArrayListPropertyEditor</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="57" alt="57"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">15</td><td class="ctr2" id="i0">15</td><td class="ctr1" id="j0">3</td><td class="ctr2" id="k0">3</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a2"><a href="StringToMapPropertyEditor.html" class="el_class">StringToMapPropertyEditor</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="65" height="10" title="31" alt="31"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h1">9</td><td class="ctr2" id="i1">9</td><td class="ctr1" id="j1">2</td><td class="ctr2" id="k1">2</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a3"><a href="StringToMapPropertyEditor$1.html" class="el_class">StringToMapPropertyEditor.new TypeReference() {...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="6" alt="6"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a1"><a href="StringToArrayListPropertyEditor$1.html" class="el_class">StringToArrayListPropertyEditor.new TypeReference() {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.propertyeditor/index.source.html b/testResult/html/stirling.software.SPDF.utils.propertyeditor/index.source.html
    new file mode 100644
    index 000000000..19358058a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.propertyeditor/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.utils.propertyeditor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.utils.propertyeditor</span></div><h1>stirling.software.SPDF.utils.propertyeditor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">100 of 100</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">9</td><td class="ctr2">9</td><td class="ctr1">26</td><td class="ctr2">26</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a0"><a href="StringToArrayListPropertyEditor.java.html" class="el_source">StringToArrayListPropertyEditor.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="63" alt="63"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h0">16</td><td class="ctr2" id="i0">16</td><td class="ctr1" id="j0">4</td><td class="ctr2" id="k0">4</td><td class="ctr1" id="l0">2</td><td class="ctr2" id="m0">2</td></tr><tr><td id="a1"><a href="StringToMapPropertyEditor.java.html" class="el_source">StringToMapPropertyEditor.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="37" alt="37"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">10</td><td class="ctr2" id="i1">10</td><td class="ctr1" id="j1">3</td><td class="ctr2" id="k1">3</td><td class="ctr1" id="l1">2</td><td class="ctr2" id="m1">2</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.validation/Validator.html b/testResult/html/stirling.software.SPDF.utils.validation/Validator.html
    new file mode 100644
    index 000000000..e33cfa997
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.validation/Validator.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Validator</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils.validation</a> &gt; <span class="el_class">Validator</span></div><h1>Validator</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">45 of 45</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a2"><a href="Validator.java.html#L10" class="el_method">validateProvider(Provider)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">9</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="Validator.java.html#L30" class="el_method">isStringEmpty(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="9" alt="9"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h1">1</td><td class="ctr2" id="i1">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="Validator.java.html#L34" class="el_method">isCollectionEmpty(Collection)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="9" alt="9"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="Validator.java.html#L7" class="el_method">Validator()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.validation/Validator.java.html b/testResult/html/stirling.software.SPDF.utils.validation/Validator.java.html
    new file mode 100644
    index 000000000..8a2340cd1
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.validation/Validator.java.html
    @@ -0,0 +1,37 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>Validator.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils.validation</a> &gt; <span class="el_source">Validator.java</span></div><h1>Validator.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils.validation;
    +
    +import java.util.Collection;
    +
    +import stirling.software.SPDF.model.provider.Provider;
    +
    +<span class="nc" id="L7">public class Validator {</span>
    +
    +    public static boolean validateProvider(Provider provider) {
    +<span class="nc bnc" id="L10" title="All 2 branches missed.">        if (provider == null) {</span>
    +<span class="nc" id="L11">            return false;</span>
    +        }
    +
    +<span class="nc bnc" id="L14" title="All 2 branches missed.">        if (isStringEmpty(provider.getClientId())) {</span>
    +<span class="nc" id="L15">            return false;</span>
    +        }
    +
    +<span class="nc bnc" id="L18" title="All 2 branches missed.">        if (isStringEmpty(provider.getClientSecret())) {</span>
    +<span class="nc" id="L19">            return false;</span>
    +        }
    +
    +<span class="nc bnc" id="L22" title="All 2 branches missed.">        if (isCollectionEmpty(provider.getScopes())) {</span>
    +<span class="nc" id="L23">            return false;</span>
    +        }
    +
    +<span class="nc" id="L26">        return true;</span>
    +    }
    +
    +    public static boolean isStringEmpty(String input) {
    +<span class="nc bnc" id="L30" title="All 4 branches missed.">        return input == null || input.isBlank();</span>
    +    }
    +
    +    public static boolean isCollectionEmpty(Collection&lt;String&gt; input) {
    +<span class="nc bnc" id="L34" title="All 4 branches missed.">        return input == null || input.isEmpty();</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.validation/index.html b/testResult/html/stirling.software.SPDF.utils.validation/index.html
    new file mode 100644
    index 000000000..88438a99e
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.validation/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.utils.validation</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.utils.validation</span></div><h1>stirling.software.SPDF.utils.validation</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">45 of 45</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="Validator.html" class="el_class">Validator</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="45" alt="45"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">4</td><td class="ctr2" id="k0">4</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils.validation/index.source.html b/testResult/html/stirling.software.SPDF.utils.validation/index.source.html
    new file mode 100644
    index 000000000..1f2298bcf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils.validation/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.utils.validation</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.utils.validation</span></div><h1>stirling.software.SPDF.utils.validation</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">45 of 45</td><td class="ctr2">0%</td><td class="bar">16 of 16</td><td class="ctr2">0%</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">4</td><td class="ctr2">4</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="Validator.java.html" class="el_source">Validator.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="45" alt="45"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="16" alt="16"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">12</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h0">12</td><td class="ctr2" id="i0">12</td><td class="ctr1" id="j0">4</td><td class="ctr2" id="k0">4</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/CheckProgramInstall.html b/testResult/html/stirling.software.SPDF.utils/CheckProgramInstall.html
    new file mode 100644
    index 000000000..5465f2864
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/CheckProgramInstall.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CheckProgramInstall</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">CheckProgramInstall</span></div><h1>CheckProgramInstall</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">61 of 61</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">7</td><td class="ctr2">7</td><td class="ctr1">20</td><td class="ctr2">20</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a1"><a href="CheckProgramInstall.java.html#L41" class="el_method">checkPythonVersion(String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">7</td><td class="ctr2" id="i1">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a4"><a href="CheckProgramInstall.java.html#L11" class="el_method">static {...}</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="102" height="10" title="17" alt="17"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="CheckProgramInstall.java.html#L22" class="el_method">getAvailablePythonCommand()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="15" alt="15"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h0">8</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="CheckProgramInstall.java.html#L57" class="el_method">isPythonAvailable()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="6" alt="6"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">2</td><td class="ctr2" id="g1">2</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="CheckProgramInstall.java.html#L9" class="el_method">CheckProgramInstall()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/CheckProgramInstall.java.html b/testResult/html/stirling.software.SPDF.utils/CheckProgramInstall.java.html
    new file mode 100644
    index 000000000..c0829aea0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/CheckProgramInstall.java.html
    @@ -0,0 +1,60 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CheckProgramInstall.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">CheckProgramInstall.java</span></div><h1>CheckProgramInstall.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.io.IOException;
    +import java.util.Arrays;
    +import java.util.List;
    +
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +
    +<span class="nc" id="L9">public class CheckProgramInstall {</span>
    +
    +<span class="nc" id="L11">    private static final List&lt;String&gt; PYTHON_COMMANDS = Arrays.asList(&quot;python3&quot;, &quot;python&quot;);</span>
    +<span class="nc" id="L12">    private static boolean pythonAvailableChecked = false;</span>
    +<span class="nc" id="L13">    private static String availablePythonCommand = null;</span>
    +
    +    /**
    +     * Checks which Python command is available and returns it.
    +     *
    +     * @return The available Python command (&quot;python3&quot; or &quot;python&quot;), or null if neither is
    +     *     available.
    +     */
    +    public static String getAvailablePythonCommand() {
    +<span class="nc bnc" id="L22" title="All 2 branches missed.">        if (!pythonAvailableChecked) {</span>
    +<span class="nc" id="L23">            availablePythonCommand =</span>
    +<span class="nc" id="L24">                    PYTHON_COMMANDS.stream()</span>
    +<span class="nc" id="L25">                            .filter(CheckProgramInstall::checkPythonVersion)</span>
    +<span class="nc" id="L26">                            .findFirst()</span>
    +<span class="nc" id="L27">                            .orElse(null);</span>
    +<span class="nc" id="L28">            pythonAvailableChecked = true;</span>
    +        }
    +<span class="nc" id="L30">        return availablePythonCommand;</span>
    +    }
    +
    +    /**
    +     * Checks if the specified command is available by running the command with --version.
    +     *
    +     * @param pythonCommand The Python command to check.
    +     * @return true if the command is available, false otherwise.
    +     */
    +    private static boolean checkPythonVersion(String pythonCommand) {
    +        try {
    +<span class="nc" id="L41">            ProcessExecutorResult result =</span>
    +<span class="nc" id="L42">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.PYTHON_OPENCV)</span>
    +<span class="nc" id="L43">                            .runCommandWithOutputHandling(</span>
    +<span class="nc" id="L44">                                    Arrays.asList(pythonCommand, &quot;--version&quot;));</span>
    +<span class="nc" id="L45">            return true; // Command succeeded, Python is available</span>
    +<span class="nc" id="L46">        } catch (IOException | InterruptedException e) {</span>
    +<span class="nc" id="L47">            return false; // Command failed, Python is not available</span>
    +        }
    +    }
    +
    +    /**
    +     * Checks if any Python command is available.
    +     *
    +     * @return true if any Python command is available, false otherwise.
    +     */
    +    public static boolean isPythonAvailable() {
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">        return getAvailablePythonCommand() != null;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/CustomHtmlSanitizer.html b/testResult/html/stirling.software.SPDF.utils/CustomHtmlSanitizer.html
    new file mode 100644
    index 000000000..efafae6ac
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/CustomHtmlSanitizer.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomHtmlSanitizer</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">CustomHtmlSanitizer</span></div><h1>CustomHtmlSanitizer</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">34 of 34</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">10</td><td class="ctr2">10</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="CustomHtmlSanitizer.java.html#L8" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="25" alt="25"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="CustomHtmlSanitizer.java.html#L18" class="el_method">sanitize(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="28" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="CustomHtmlSanitizer.java.html#L7" class="el_method">CustomHtmlSanitizer()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/CustomHtmlSanitizer.java.html b/testResult/html/stirling.software.SPDF.utils/CustomHtmlSanitizer.java.html
    new file mode 100644
    index 000000000..cc25c1b3c
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/CustomHtmlSanitizer.java.html
    @@ -0,0 +1,22 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>CustomHtmlSanitizer.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">CustomHtmlSanitizer.java</span></div><h1>CustomHtmlSanitizer.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import org.owasp.html.HtmlPolicyBuilder;
    +import org.owasp.html.PolicyFactory;
    +import org.owasp.html.Sanitizers;
    +
    +<span class="nc" id="L7">public class CustomHtmlSanitizer {</span>
    +<span class="nc" id="L8">    private static final PolicyFactory POLICY =</span>
    +            Sanitizers.FORMATTING
    +<span class="nc" id="L10">                    .and(Sanitizers.BLOCKS)</span>
    +<span class="nc" id="L11">                    .and(Sanitizers.STYLES)</span>
    +<span class="nc" id="L12">                    .and(Sanitizers.LINKS)</span>
    +<span class="nc" id="L13">                    .and(Sanitizers.TABLES)</span>
    +<span class="nc" id="L14">                    .and(Sanitizers.IMAGES)</span>
    +<span class="nc" id="L15">                    .and(new HtmlPolicyBuilder().disallowElements(&quot;noscript&quot;).toFactory());</span>
    +
    +    public static String sanitize(String html) {
    +<span class="nc" id="L18">        String htmlAfter = POLICY.sanitize(html);</span>
    +<span class="nc" id="L19">        return htmlAfter;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/ErrorUtils.html b/testResult/html/stirling.software.SPDF.utils/ErrorUtils.html
    new file mode 100644
    index 000000000..cc851316f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/ErrorUtils.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ErrorUtils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">ErrorUtils</span></div><h1>ErrorUtils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">59 of 59</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a2"><a href="ErrorUtils.java.html#L22" class="el_method">exceptionToModelView(Model, Exception)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="30" alt="30"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ErrorUtils.java.html#L12" class="el_method">exceptionToModel(Model, Exception)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="104" height="10" title="26" alt="26"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="ErrorUtils.java.html#L9" class="el_method">ErrorUtils()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/ErrorUtils.java.html b/testResult/html/stirling.software.SPDF.utils/ErrorUtils.java.html
    new file mode 100644
    index 000000000..cefb04e8d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/ErrorUtils.java.html
    @@ -0,0 +1,32 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ErrorUtils.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">ErrorUtils.java</span></div><h1>ErrorUtils.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.io.PrintWriter;
    +import java.io.StringWriter;
    +
    +import org.springframework.ui.Model;
    +import org.springframework.web.servlet.ModelAndView;
    +
    +<span class="nc" id="L9">public class ErrorUtils {</span>
    +
    +    public static Model exceptionToModel(Model model, Exception ex) {
    +<span class="nc" id="L12">        StringWriter sw = new StringWriter();</span>
    +<span class="nc" id="L13">        ex.printStackTrace(new PrintWriter(sw));</span>
    +<span class="nc" id="L14">        String stackTrace = sw.toString();</span>
    +
    +<span class="nc" id="L16">        model.addAttribute(&quot;errorMessage&quot;, ex.getMessage());</span>
    +<span class="nc" id="L17">        model.addAttribute(&quot;stackTrace&quot;, stackTrace);</span>
    +<span class="nc" id="L18">        return model;</span>
    +    }
    +
    +    public static ModelAndView exceptionToModelView(Model model, Exception ex) {
    +<span class="nc" id="L22">        StringWriter sw = new StringWriter();</span>
    +<span class="nc" id="L23">        ex.printStackTrace(new PrintWriter(sw));</span>
    +<span class="nc" id="L24">        String stackTrace = sw.toString();</span>
    +
    +<span class="nc" id="L26">        ModelAndView modelAndView = new ModelAndView();</span>
    +<span class="nc" id="L27">        modelAndView.addObject(&quot;errorMessage&quot;, ex.getMessage());</span>
    +<span class="nc" id="L28">        modelAndView.addObject(&quot;stackTrace&quot;, stackTrace);</span>
    +<span class="nc" id="L29">        return modelAndView;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/FileInfo.html b/testResult/html/stirling.software.SPDF.utils/FileInfo.html
    new file mode 100644
    index 000000000..368d30467
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/FileInfo.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileInfo</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">FileInfo</span></div><h1>FileInfo</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">91 of 91</td><td class="ctr2">0%</td><td class="bar">6 of 6</td><td class="ctr2">0%</td><td class="ctr1">8</td><td class="ctr2">8</td><td class="ctr1">12</td><td class="ctr2">12</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a2"><a href="FileInfo.java.html#L30" class="el_method">getFormattedFileSize()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="71" alt="71"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">7</td><td class="ctr2" id="i0">7</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="FileInfo.java.html#L25" class="el_method">getFilePathAsPath()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="6" alt="6"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a3"><a href="FileInfo.java.html#L43" class="el_method">getFormattedModificationDate()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="5" alt="5"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="FileInfo.java.html#L48" class="el_method">getFormattedCreationDate()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="5" alt="5"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="FileInfo.java.html#L15" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/FileInfo.java.html b/testResult/html/stirling.software.SPDF.utils/FileInfo.java.html
    new file mode 100644
    index 000000000..c9406762d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/FileInfo.java.html
    @@ -0,0 +1,51 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileInfo.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">FileInfo.java</span></div><h1>FileInfo.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.time.LocalDateTime;
    +import java.time.format.DateTimeFormatter;
    +import java.util.Locale;
    +
    +import lombok.AllArgsConstructor;
    +import lombok.Data;
    +
    +@AllArgsConstructor
    +@Data
    +public class FileInfo {
    +<span class="nc" id="L15">    private static final DateTimeFormatter DATE_FORMATTER =</span>
    +<span class="nc" id="L16">            DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm:ss&quot;);</span>
    +    private String fileName;
    +    private String filePath;
    +    private LocalDateTime modificationDate;
    +    private long fileSize;
    +    private LocalDateTime creationDate;
    +
    +    // Converts the file path string to a Path object.
    +    public Path getFilePathAsPath() {
    +<span class="nc" id="L25">        return Paths.get(filePath);</span>
    +    }
    +
    +    // Formats the file size into a human-readable string.
    +    public String getFormattedFileSize() {
    +<span class="nc bnc" id="L30" title="All 2 branches missed.">        if (fileSize &gt;= 1024 * 1024 * 1024) {</span>
    +<span class="nc" id="L31">            return String.format(Locale.US, &quot;%.2f GB&quot;, fileSize / (1024.0 * 1024 * 1024));</span>
    +<span class="nc bnc" id="L32" title="All 2 branches missed.">        } else if (fileSize &gt;= 1024 * 1024) {</span>
    +<span class="nc" id="L33">            return String.format(Locale.US, &quot;%.2f MB&quot;, fileSize / (1024.0 * 1024));</span>
    +<span class="nc bnc" id="L34" title="All 2 branches missed.">        } else if (fileSize &gt;= 1024) {</span>
    +<span class="nc" id="L35">            return String.format(Locale.US, &quot;%.2f KB&quot;, fileSize / 1024.0);</span>
    +        } else {
    +<span class="nc" id="L37">            return String.format(&quot;%d Bytes&quot;, fileSize);</span>
    +        }
    +    }
    +
    +    // Formats the modification date to a string.
    +    public String getFormattedModificationDate() {
    +<span class="nc" id="L43">        return modificationDate.format(DATE_FORMATTER);</span>
    +    }
    +
    +    // Formats the creation date to a string.
    +    public String getFormattedCreationDate() {
    +<span class="nc" id="L48">        return creationDate.format(DATE_FORMATTER);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/FileMonitor.html b/testResult/html/stirling.software.SPDF.utils/FileMonitor.html
    new file mode 100644
    index 000000000..3b82379ae
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/FileMonitor.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileMonitor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">FileMonitor</span></div><h1>FileMonitor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">369 of 369</td><td class="ctr2">0%</td><td class="bar">40 of 40</td><td class="ctr2">0%</td><td class="ctr1">31</td><td class="ctr2">31</td><td class="ctr1">93</td><td class="ctr2">93</td><td class="ctr1">11</td><td class="ctr2">11</td></tr></tfoot><tbody><tr><td id="a7"><a href="FileMonitor.java.html#L61" class="el_method">recursivelyRegisterEntry(Path)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="73" alt="73"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">6</td><td class="ctr2" id="g1">6</td><td class="ctr1" id="h3">14</td><td class="ctr2" id="i3">14</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a10"><a href="FileMonitor.java.html#L86" class="el_method">trackFiles()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="116" height="10" title="71" alt="71"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h1">19</td><td class="ctr2" id="i1">19</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a5"><a href="FileMonitor.java.html#L173" class="el_method">isFileReadyForProcessing(Path)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="106" height="10" title="65" alt="65"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">5</td><td class="ctr2" id="g3">5</td><td class="ctr1" id="h0">20</td><td class="ctr2" id="i0">20</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a6"><a href="FileMonitor.java.html#L107" class="el_method">lambda$trackFiles$0(Path, WatchEvent)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="55" alt="55"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h2">17</td><td class="ctr2" id="i2">17</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="FileMonitor.java.html#L45" class="el_method">FileMonitor(Predicate, RuntimePathConfig)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="65" height="10" title="40" alt="40"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h4">10</td><td class="ctr2" id="i4">10</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a1"><a href="FileMonitor.java.html#L144" class="el_method">handleDirectoryCreation(Path)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="42" height="10" title="26" alt="26"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h5">3</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="FileMonitor.java.html#L149" class="el_method">handleFileRemoval(Path)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="11" alt="11"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h6">3</td><td class="ctr2" id="i6">3</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a2"><a href="FileMonitor.java.html#L154" class="el_method">handleFileCreation(Path)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="18" height="10" title="11" alt="11"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="FileMonitor.java.html#L57" class="el_method">shouldNotProcess(Path)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="9" alt="9"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a3"><a href="FileMonitor.java.html#L160" class="el_method">handleFileModification(Path)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h8">2</td><td class="ctr2" id="i8">2</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a9"><a href="FileMonitor.java.html#L25" class="el_method">static {...}</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/FileMonitor.java.html b/testResult/html/stirling.software.SPDF.utils/FileMonitor.java.html
    new file mode 100644
    index 000000000..341c77a59
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/FileMonitor.java.html
    @@ -0,0 +1,206 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileMonitor.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">FileMonitor.java</span></div><h1>FileMonitor.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import static java.nio.file.StandardWatchEventKinds.*;
    +
    +import java.io.IOException;
    +import java.io.RandomAccessFile;
    +import java.nio.channels.FileChannel;
    +import java.nio.channels.FileLock;
    +import java.nio.file.*;
    +import java.util.*;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.function.Predicate;
    +import java.util.stream.Stream;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.beans.factory.annotation.Qualifier;
    +import org.springframework.scheduling.annotation.Scheduled;
    +import org.springframework.stereotype.Component;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.RuntimePathConfig;
    +
    +@Component
    +<span class="nc" id="L25">@Slf4j</span>
    +public class FileMonitor {
    +
    +    private final Map&lt;Path, WatchKey&gt; path2KeyMapping;
    +    private final Set&lt;Path&gt; newlyDiscoveredFiles;
    +    private final ConcurrentHashMap.KeySetView&lt;Path, Boolean&gt; readyForProcessingFiles;
    +    private final WatchService watchService;
    +    private final Predicate&lt;Path&gt; pathFilter;
    +    private final Path rootDir;
    +    private Set&lt;Path&gt; stagingFiles;
    +
    +    /**
    +     * @param rootDirectory the root directory to monitor
    +     * @param pathFilter the filter to apply to the paths, return true if the path should be
    +     *     monitored, false otherwise
    +     */
    +    @Autowired
    +    public FileMonitor(
    +            @Qualifier(&quot;directoryFilter&quot;) Predicate&lt;Path&gt; pathFilter,
    +            RuntimePathConfig runtimePathConfig)
    +<span class="nc" id="L45">            throws IOException {</span>
    +<span class="nc" id="L46">        this.newlyDiscoveredFiles = new HashSet&lt;&gt;();</span>
    +<span class="nc" id="L47">        this.path2KeyMapping = new HashMap&lt;&gt;();</span>
    +<span class="nc" id="L48">        this.stagingFiles = new HashSet&lt;&gt;();</span>
    +<span class="nc" id="L49">        this.pathFilter = pathFilter;</span>
    +<span class="nc" id="L50">        this.readyForProcessingFiles = ConcurrentHashMap.newKeySet();</span>
    +<span class="nc" id="L51">        this.watchService = FileSystems.getDefault().newWatchService();</span>
    +<span class="nc" id="L52">        log.info(&quot;Monitoring directory: {}&quot;, runtimePathConfig.getPipelineWatchedFoldersPath());</span>
    +<span class="nc" id="L53">        this.rootDir = Path.of(runtimePathConfig.getPipelineWatchedFoldersPath());</span>
    +<span class="nc" id="L54">    }</span>
    +
    +    private boolean shouldNotProcess(Path path) {
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">        return !pathFilter.test(path);</span>
    +    }
    +
    +    private void recursivelyRegisterEntry(Path dir) throws IOException {
    +<span class="nc" id="L61">        WatchKey key = dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);</span>
    +<span class="nc" id="L62">        path2KeyMapping.put(dir, key);</span>
    +<span class="nc" id="L63">        log.info(&quot;Registered directory: {}&quot;, dir);</span>
    +
    +<span class="nc" id="L65">        try (Stream&lt;Path&gt; directoryVisitor = Files.walk(dir, 1)) {</span>
    +<span class="nc" id="L66">            final Iterator&lt;Path&gt; iterator = directoryVisitor.iterator();</span>
    +<span class="nc bnc" id="L67" title="All 2 branches missed.">            while (iterator.hasNext()) {</span>
    +<span class="nc" id="L68">                Path path = iterator.next();</span>
    +<span class="nc bnc" id="L69" title="All 4 branches missed.">                if (path.equals(dir) || shouldNotProcess(path)) continue;</span>
    +
    +<span class="nc bnc" id="L71" title="All 2 branches missed.">                if (Files.isDirectory(path)) {</span>
    +<span class="nc" id="L72">                    recursivelyRegisterEntry(path);</span>
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">                } else if (Files.isRegularFile(path)) {</span>
    +<span class="nc" id="L74">                    handleFileCreation(path);</span>
    +                }
    +<span class="nc" id="L76">            }</span>
    +        }
    +<span class="nc" id="L78">    }</span>
    +
    +    @Scheduled(fixedRate = 5000)
    +    public void trackFiles() {
    +        /*
    +         All files observed changes in the last iteration will be considered as staging files.
    +         If those files are not modified in current iteration, they will be considered as ready for processing.
    +        */
    +<span class="nc" id="L86">        stagingFiles = new HashSet&lt;&gt;(newlyDiscoveredFiles);</span>
    +<span class="nc" id="L87">        readyForProcessingFiles.clear();</span>
    +
    +<span class="nc bnc" id="L89" title="All 2 branches missed.">        if (path2KeyMapping.isEmpty()) {</span>
    +<span class="nc" id="L90">            log.warn(&quot;not monitoring any directory, even the root directory itself: {}&quot;, rootDir);</span>
    +<span class="nc bnc" id="L91" title="All 2 branches missed.">            if (Files.exists(</span>
    +                    rootDir)) { // if the root directory exists, re-register the root directory
    +                try {
    +<span class="nc" id="L94">                    recursivelyRegisterEntry(rootDir);</span>
    +<span class="nc" id="L95">                } catch (IOException e) {</span>
    +<span class="nc" id="L96">                    log.error(&quot;unable to register monitoring&quot;, e);</span>
    +<span class="nc" id="L97">                }</span>
    +            }
    +        }
    +
    +        WatchKey key;
    +<span class="nc bnc" id="L102" title="All 2 branches missed.">        while ((key = watchService.poll()) != null) {</span>
    +<span class="nc" id="L103">            final Path watchingDir = (Path) key.watchable();</span>
    +<span class="nc" id="L104">            key.pollEvents()</span>
    +<span class="nc" id="L105">                    .forEach(</span>
    +                            (evt) -&gt; {
    +<span class="nc" id="L107">                                final Path path = (Path) evt.context();</span>
    +<span class="nc" id="L108">                                final WatchEvent.Kind&lt;?&gt; kind = evt.kind();</span>
    +<span class="nc bnc" id="L109" title="All 2 branches missed.">                                if (shouldNotProcess(path)) return;</span>
    +
    +                                try {
    +<span class="nc bnc" id="L112" title="All 2 branches missed.">                                    if (Files.isDirectory(path)) {</span>
    +<span class="nc bnc" id="L113" title="All 2 branches missed.">                                        if (kind == ENTRY_CREATE) {</span>
    +<span class="nc" id="L114">                                            handleDirectoryCreation(path);</span>
    +                                        }
    +                                        /*
    +                                         we don't need to handle directory deletion or modification
    +                                         - directory deletion will be handled by key.reset()
    +                                         - directory modification indicates a new file creation or deletion, which is handled by below
    +                                        */
    +                                    }
    +<span class="nc" id="L122">                                    Path relativePathFromRoot = watchingDir.resolve(path);</span>
    +<span class="nc bnc" id="L123" title="All 2 branches missed.">                                    if (kind == ENTRY_CREATE) {</span>
    +<span class="nc" id="L124">                                        handleFileCreation(relativePathFromRoot);</span>
    +<span class="nc bnc" id="L125" title="All 2 branches missed.">                                    } else if (kind == ENTRY_DELETE) {</span>
    +<span class="nc" id="L126">                                        handleFileRemoval(relativePathFromRoot);</span>
    +<span class="nc bnc" id="L127" title="All 2 branches missed.">                                    } else if (kind == ENTRY_MODIFY) {</span>
    +<span class="nc" id="L128">                                        handleFileModification(relativePathFromRoot);</span>
    +                                    }
    +<span class="nc" id="L130">                                } catch (Exception e) {</span>
    +<span class="nc" id="L131">                                    log.error(&quot;Error while processing file: {}&quot;, path, e);</span>
    +<span class="nc" id="L132">                                }</span>
    +<span class="nc" id="L133">                            });</span>
    +
    +<span class="nc" id="L135">            boolean isKeyValid = key.reset();</span>
    +<span class="nc bnc" id="L136" title="All 2 branches missed.">            if (!isKeyValid) { // key is invalid when the directory itself is no longer exists</span>
    +<span class="nc" id="L137">                path2KeyMapping.remove((Path) key.watchable());</span>
    +            }
    +<span class="nc" id="L139">        }</span>
    +<span class="nc" id="L140">        readyForProcessingFiles.addAll(stagingFiles);</span>
    +<span class="nc" id="L141">    }</span>
    +
    +    private void handleDirectoryCreation(Path dir) throws IOException {
    +<span class="nc" id="L144">        WatchKey key = dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);</span>
    +<span class="nc" id="L145">        path2KeyMapping.put(dir, key);</span>
    +<span class="nc" id="L146">    }</span>
    +
    +    private void handleFileRemoval(Path path) {
    +<span class="nc" id="L149">        newlyDiscoveredFiles.remove(path);</span>
    +<span class="nc" id="L150">        stagingFiles.remove(path);</span>
    +<span class="nc" id="L151">    }</span>
    +
    +    private void handleFileCreation(Path path) {
    +<span class="nc" id="L154">        newlyDiscoveredFiles.add(path);</span>
    +<span class="nc" id="L155">        stagingFiles.remove(path);</span>
    +<span class="nc" id="L156">    }</span>
    +
    +    private void handleFileModification(Path path) {
    +        // the logic is the same
    +<span class="nc" id="L160">        handleFileCreation(path);</span>
    +<span class="nc" id="L161">    }</span>
    +
    +    /**
    +     * Check if the file is ready for processing.
    +     *
    +     * &lt;p&gt;A file is ready for processing if it is not being modified for 5000ms.
    +     *
    +     * @param path the path of the file
    +     * @return true if the file is ready for processing, false otherwise
    +     */
    +    public boolean isFileReadyForProcessing(Path path) {
    +        // 1. Check FileMonitor's ready list
    +<span class="nc" id="L173">        boolean isReady = readyForProcessingFiles.contains(path.toAbsolutePath());</span>
    +
    +        // 2. Check last modified timestamp
    +<span class="nc bnc" id="L176" title="All 2 branches missed.">        if (!isReady) {</span>
    +            try {
    +<span class="nc" id="L178">                long lastModified = Files.getLastModifiedTime(path).toMillis();</span>
    +<span class="nc" id="L179">                long currentTime = System.currentTimeMillis();</span>
    +<span class="nc bnc" id="L180" title="All 2 branches missed.">                isReady = (currentTime - lastModified) &gt; 5000;</span>
    +<span class="nc" id="L181">            } catch (IOException e) {</span>
    +<span class="nc" id="L182">                log.info(&quot;Timestamp check failed for {}&quot;, path, e);</span>
    +<span class="nc" id="L183">            }</span>
    +        }
    +
    +        // 3. Direct file lock check
    +<span class="nc bnc" id="L187" title="All 2 branches missed.">        if (isReady) {</span>
    +<span class="nc" id="L188">            try (RandomAccessFile raf = new RandomAccessFile(path.toFile(), &quot;rw&quot;);</span>
    +<span class="nc" id="L189">                    FileChannel channel = raf.getChannel()) {</span>
    +                // Try acquiring an exclusive lock
    +<span class="nc" id="L191">                FileLock lock = channel.tryLock();</span>
    +<span class="nc bnc" id="L192" title="All 2 branches missed.">                if (lock == null) {</span>
    +<span class="nc" id="L193">                    isReady = false;</span>
    +                } else {
    +<span class="nc" id="L195">                    lock.release();</span>
    +                }
    +<span class="nc" id="L197">            } catch (IOException e) {</span>
    +<span class="nc" id="L198">                log.info(&quot;File lock detected on {}&quot;, path);</span>
    +<span class="nc" id="L199">                isReady = false;</span>
    +<span class="nc" id="L200">            }</span>
    +        }
    +
    +<span class="nc" id="L203">        return isReady;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/FileToPdf$1.html b/testResult/html/stirling.software.SPDF.utils/FileToPdf$1.html
    new file mode 100644
    index 000000000..ead256f39
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/FileToPdf$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileToPdf.new SimpleFileVisitor() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">FileToPdf.new SimpleFileVisitor() {...}</span></div><h1>FileToPdf.new SimpleFileVisitor() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">11 of 11</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="FileToPdf.java.html#L140" class="el_method">visitFile(Path, BasicFileAttributes)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="FileToPdf.java.html#L147" class="el_method">postVisitDirectory(Path, IOException)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="FileToPdf.java.html#L136" class="el_method">{...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="90" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/FileToPdf.html b/testResult/html/stirling.software.SPDF.utils/FileToPdf.html
    new file mode 100644
    index 000000000..e8125e4a8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/FileToPdf.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileToPdf</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">FileToPdf</span></div><h1>FileToPdf</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">420 of 420</td><td class="ctr2">0%</td><td class="bar">38 of 38</td><td class="ctr2">0%</td><td class="ctr1">30</td><td class="ctr2">30</td><td class="ctr1">104</td><td class="ctr2">104</td><td class="ctr1">11</td><td class="ctr2">11</td></tr></tfoot><tbody><tr><td id="a0"><a href="FileToPdf.java.html#L32" class="el_method">convertHtmlToPdf(String, HTMLToPdfRequest, byte[], String, boolean)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="121" alt="121"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f3">4</td><td class="ctr2" id="g3">4</td><td class="ctr1" id="h0">31</td><td class="ctr2" id="i0">31</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a9"><a href="FileToPdf.java.html#L154" class="el_method">unzipAndGetMainHtml(byte[])</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="98" height="10" title="99" alt="99"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">7</td><td class="ctr2" id="g0">7</td><td class="ctr1" id="h1">24</td><td class="ctr2" id="i1">24</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a7"><a href="FileToPdf.java.html#L83" class="el_method">sanitizeHtmlFilesInZip(Path, boolean)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="81" height="10" title="82" alt="82"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h2">22</td><td class="ctr2" id="i2">22</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a8"><a href="FileToPdf.java.html#L192" class="el_method">sanitizeZipFilename(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="41" height="10" title="42" alt="42"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="8" alt="8"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">5</td><td class="ctr2" id="g2">5</td><td class="ctr1" id="h4">8</td><td class="ctr2" id="i4">8</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="FileToPdf.java.html#L120" class="el_method">lambda$zipDirectory$1(Path, ZipOutputStream, Path)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="25" alt="25"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h3">9</td><td class="ctr2" id="i3">9</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a10"><a href="FileToPdf.java.html#L114" class="el_method">zipDirectory(Path, Path)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="20" alt="20"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h5">6</td><td class="ctr2" id="i5">6</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="FileToPdf.java.html#L117" class="el_method">lambda$zipDirectory$0(Path)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="9" alt="9"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a6"><a href="FileToPdf.java.html#L78" class="el_method">sanitizeHtmlContent(String, boolean)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a1"><a href="FileToPdf.java.html#L134" class="el_method">deleteDirectory(Path)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="7" alt="7"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h6">2</td><td class="ctr2" id="i6">2</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a3"><a href="FileToPdf.java.html#L174" class="el_method">lambda$unzipAndGetMainHtml$2(Path)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a2"><a href="FileToPdf.java.html#L22" class="el_method">FileToPdf()</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="3" alt="3"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/FileToPdf.java.html b/testResult/html/stirling.software.SPDF.utils/FileToPdf.java.html
    new file mode 100644
    index 000000000..29b4defa9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/FileToPdf.java.html
    @@ -0,0 +1,208 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>FileToPdf.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">FileToPdf.java</span></div><h1>FileToPdf.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.io.*;
    +import java.nio.charset.StandardCharsets;
    +import java.nio.file.FileVisitResult;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.SimpleFileVisitor;
    +import java.nio.file.attribute.BasicFileAttributes;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.stream.Stream;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipInputStream;
    +import java.util.zip.ZipOutputStream;
    +
    +import io.github.pixee.security.ZipSecurity;
    +
    +import stirling.software.SPDF.model.api.converters.HTMLToPdfRequest;
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +
    +<span class="nc" id="L22">public class FileToPdf {</span>
    +
    +    public static byte[] convertHtmlToPdf(
    +            String weasyprintPath,
    +            HTMLToPdfRequest request,
    +            byte[] fileBytes,
    +            String fileName,
    +            boolean disableSanitize)
    +            throws IOException, InterruptedException {
    +
    +<span class="nc" id="L32">        Path tempOutputFile = Files.createTempFile(&quot;output_&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L33">        Path tempInputFile = null;</span>
    +        byte[] pdfBytes;
    +        try {
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">            if (fileName.endsWith(&quot;.html&quot;)) {</span>
    +<span class="nc" id="L37">                tempInputFile = Files.createTempFile(&quot;input_&quot;, &quot;.html&quot;);</span>
    +<span class="nc" id="L38">                String sanitizedHtml =</span>
    +<span class="nc" id="L39">                        sanitizeHtmlContent(</span>
    +                                new String(fileBytes, StandardCharsets.UTF_8), disableSanitize);
    +<span class="nc" id="L41">                Files.write(tempInputFile, sanitizedHtml.getBytes(StandardCharsets.UTF_8));</span>
    +<span class="nc bnc" id="L42" title="All 2 branches missed.">            } else if (fileName.endsWith(&quot;.zip&quot;)) {</span>
    +<span class="nc" id="L43">                tempInputFile = Files.createTempFile(&quot;input_&quot;, &quot;.zip&quot;);</span>
    +<span class="nc" id="L44">                Files.write(tempInputFile, fileBytes);</span>
    +<span class="nc" id="L45">                sanitizeHtmlFilesInZip(tempInputFile, disableSanitize);</span>
    +            } else {
    +<span class="nc" id="L47">                throw new IllegalArgumentException(&quot;Unsupported file format: &quot; + fileName);</span>
    +            }
    +
    +<span class="nc" id="L50">            List&lt;String&gt; command = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L51">            command.add(weasyprintPath);</span>
    +<span class="nc" id="L52">            command.add(&quot;-e&quot;);</span>
    +<span class="nc" id="L53">            command.add(&quot;utf-8&quot;);</span>
    +<span class="nc" id="L54">            command.add(&quot;-v&quot;);</span>
    +<span class="nc" id="L55">            command.add(&quot;--pdf-forms&quot;);</span>
    +<span class="nc" id="L56">            command.add(tempInputFile.toString());</span>
    +<span class="nc" id="L57">            command.add(tempOutputFile.toString());</span>
    +
    +<span class="nc" id="L59">            ProcessExecutorResult returnCode =</span>
    +<span class="nc" id="L60">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.WEASYPRINT)</span>
    +<span class="nc" id="L61">                            .runCommandWithOutputHandling(command);</span>
    +
    +<span class="nc" id="L63">            pdfBytes = Files.readAllBytes(tempOutputFile);</span>
    +<span class="nc" id="L64">        } catch (IOException e) {</span>
    +<span class="nc" id="L65">            pdfBytes = Files.readAllBytes(tempOutputFile);</span>
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">            if (pdfBytes.length &lt; 1) {</span>
    +<span class="nc" id="L67">                throw e;</span>
    +            }
    +        } finally {
    +<span class="nc" id="L70">            Files.deleteIfExists(tempOutputFile);</span>
    +<span class="nc" id="L71">            Files.deleteIfExists(tempInputFile);</span>
    +        }
    +
    +<span class="nc" id="L74">        return pdfBytes;</span>
    +    }
    +
    +    private static String sanitizeHtmlContent(String htmlContent, boolean disableSanitize) {
    +<span class="nc bnc" id="L78" title="All 2 branches missed.">        return (!disableSanitize) ? CustomHtmlSanitizer.sanitize(htmlContent) : htmlContent;</span>
    +    }
    +
    +    private static void sanitizeHtmlFilesInZip(Path zipFilePath, boolean disableSanitize)
    +            throws IOException {
    +<span class="nc" id="L83">        Path tempUnzippedDir = Files.createTempDirectory(&quot;unzipped_&quot;);</span>
    +<span class="nc" id="L84">        try (ZipInputStream zipIn =</span>
    +<span class="nc" id="L85">                ZipSecurity.createHardenedInputStream(</span>
    +<span class="nc" id="L86">                        new ByteArrayInputStream(Files.readAllBytes(zipFilePath)))) {</span>
    +<span class="nc" id="L87">            ZipEntry entry = zipIn.getNextEntry();</span>
    +<span class="nc bnc" id="L88" title="All 2 branches missed.">            while (entry != null) {</span>
    +<span class="nc" id="L89">                Path filePath = tempUnzippedDir.resolve(sanitizeZipFilename(entry.getName()));</span>
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">                if (!entry.isDirectory()) {</span>
    +<span class="nc" id="L91">                    Files.createDirectories(filePath.getParent());</span>
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">                    if (entry.getName().toLowerCase().endsWith(&quot;.html&quot;)</span>
    +<span class="nc bnc" id="L93" title="All 2 branches missed.">                            || entry.getName().toLowerCase().endsWith(&quot;.htm&quot;)) {</span>
    +<span class="nc" id="L94">                        String content = new String(zipIn.readAllBytes(), StandardCharsets.UTF_8);</span>
    +<span class="nc" id="L95">                        String sanitizedContent = sanitizeHtmlContent(content, disableSanitize);</span>
    +<span class="nc" id="L96">                        Files.write(filePath, sanitizedContent.getBytes(StandardCharsets.UTF_8));</span>
    +<span class="nc" id="L97">                    } else {</span>
    +<span class="nc" id="L98">                        Files.copy(zipIn, filePath);</span>
    +                    }
    +                }
    +<span class="nc" id="L101">                zipIn.closeEntry();</span>
    +<span class="nc" id="L102">                entry = zipIn.getNextEntry();</span>
    +<span class="nc" id="L103">            }</span>
    +        }
    +
    +        // Repack the sanitized files
    +<span class="nc" id="L107">        zipDirectory(tempUnzippedDir, zipFilePath);</span>
    +
    +        // Clean up
    +<span class="nc" id="L110">        deleteDirectory(tempUnzippedDir);</span>
    +<span class="nc" id="L111">    }</span>
    +
    +    private static void zipDirectory(Path sourceDir, Path zipFilePath) throws IOException {
    +<span class="nc" id="L114">        try (ZipOutputStream zos =</span>
    +<span class="nc" id="L115">                new ZipOutputStream(new FileOutputStream(zipFilePath.toFile()))) {</span>
    +<span class="nc" id="L116">            Files.walk(sourceDir)</span>
    +<span class="nc bnc" id="L117" title="All 2 branches missed.">                    .filter(path -&gt; !Files.isDirectory(path))</span>
    +<span class="nc" id="L118">                    .forEach(</span>
    +                            path -&gt; {
    +<span class="nc" id="L120">                                ZipEntry zipEntry =</span>
    +<span class="nc" id="L121">                                        new ZipEntry(sourceDir.relativize(path).toString());</span>
    +                                try {
    +<span class="nc" id="L123">                                    zos.putNextEntry(zipEntry);</span>
    +<span class="nc" id="L124">                                    Files.copy(path, zos);</span>
    +<span class="nc" id="L125">                                    zos.closeEntry();</span>
    +<span class="nc" id="L126">                                } catch (IOException e) {</span>
    +<span class="nc" id="L127">                                    throw new UncheckedIOException(e);</span>
    +<span class="nc" id="L128">                                }</span>
    +<span class="nc" id="L129">                            });</span>
    +        }
    +<span class="nc" id="L131">    }</span>
    +
    +    private static void deleteDirectory(Path dir) throws IOException {
    +<span class="nc" id="L134">        Files.walkFileTree(</span>
    +                dir,
    +<span class="nc" id="L136">                new SimpleFileVisitor&lt;Path&gt;() {</span>
    +                    @Override
    +                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
    +                            throws IOException {
    +<span class="nc" id="L140">                        Files.delete(file);</span>
    +<span class="nc" id="L141">                        return FileVisitResult.CONTINUE;</span>
    +                    }
    +
    +                    @Override
    +                    public FileVisitResult postVisitDirectory(Path dir, IOException exc)
    +                            throws IOException {
    +<span class="nc" id="L147">                        Files.delete(dir);</span>
    +<span class="nc" id="L148">                        return FileVisitResult.CONTINUE;</span>
    +                    }
    +                });
    +<span class="nc" id="L151">    }</span>
    +
    +    private static Path unzipAndGetMainHtml(byte[] fileBytes) throws IOException {
    +<span class="nc" id="L154">        Path tempDirectory = Files.createTempDirectory(&quot;unzipped_&quot;);</span>
    +<span class="nc" id="L155">        try (ZipInputStream zipIn =</span>
    +<span class="nc" id="L156">                ZipSecurity.createHardenedInputStream(new ByteArrayInputStream(fileBytes))) {</span>
    +<span class="nc" id="L157">            ZipEntry entry = zipIn.getNextEntry();</span>
    +<span class="nc bnc" id="L158" title="All 2 branches missed.">            while (entry != null) {</span>
    +<span class="nc" id="L159">                Path filePath = tempDirectory.resolve(sanitizeZipFilename(entry.getName()));</span>
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">                if (entry.isDirectory()) {</span>
    +<span class="nc" id="L161">                    Files.createDirectories(filePath); // Explicitly create the directory structure</span>
    +                } else {
    +<span class="nc" id="L163">                    Files.createDirectories(</span>
    +<span class="nc" id="L164">                            filePath.getParent()); // Create parent directories if they don't exist</span>
    +<span class="nc" id="L165">                    Files.copy(zipIn, filePath);</span>
    +                }
    +<span class="nc" id="L167">                zipIn.closeEntry();</span>
    +<span class="nc" id="L168">                entry = zipIn.getNextEntry();</span>
    +<span class="nc" id="L169">            }</span>
    +        }
    +
    +        // Search for the main HTML file.
    +<span class="nc" id="L173">        try (Stream&lt;Path&gt; walk = Files.walk(tempDirectory)) {</span>
    +<span class="nc" id="L174">            List&lt;Path&gt; htmlFiles = walk.filter(file -&gt; file.toString().endsWith(&quot;.html&quot;)).toList();</span>
    +
    +<span class="nc bnc" id="L176" title="All 2 branches missed.">            if (htmlFiles.isEmpty()) {</span>
    +<span class="nc" id="L177">                throw new IOException(&quot;No HTML files found in the unzipped directory.&quot;);</span>
    +            }
    +
    +            // Prioritize 'index.html' if it exists, otherwise use the first .html file
    +<span class="nc bnc" id="L181" title="All 2 branches missed.">            for (Path htmlFile : htmlFiles) {</span>
    +<span class="nc bnc" id="L182" title="All 2 branches missed.">                if (&quot;index.html&quot;.equals(htmlFile.getFileName().toString())) {</span>
    +<span class="nc" id="L183">                    return htmlFile;</span>
    +                }
    +<span class="nc" id="L185">            }</span>
    +
    +<span class="nc" id="L187">            return htmlFiles.get(0);</span>
    +<span class="nc bnc" id="L188" title="All 2 branches missed.">        }</span>
    +    }
    +
    +    static String sanitizeZipFilename(String entryName) {
    +<span class="nc bnc" id="L192" title="All 4 branches missed.">        if (entryName == null || entryName.trim().isEmpty()) {</span>
    +<span class="nc" id="L193">            return &quot;&quot;;</span>
    +        }
    +        // Remove any drive letters (e.g., &quot;C:\&quot;) and leading forward/backslashes
    +<span class="nc" id="L196">        entryName = entryName.replaceAll(&quot;^[a-zA-Z]:[\\\\/]+&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L197">        entryName = entryName.replaceAll(&quot;^[\\\\/]+&quot;, &quot;&quot;);</span>
    +
    +        // Recursively remove path traversal sequences
    +<span class="nc bnc" id="L200" title="All 4 branches missed.">        while (entryName.contains(&quot;../&quot;) || entryName.contains(&quot;..\\&quot;)) {</span>
    +<span class="nc" id="L201">            entryName = entryName.replace(&quot;../&quot;, &quot;&quot;).replace(&quot;..\\&quot;, &quot;&quot;);</span>
    +        }
    +        // Normalize all backslashes to forward slashes
    +<span class="nc" id="L204">        entryName = entryName.replaceAll(&quot;\\\\&quot;, &quot;/&quot;);</span>
    +<span class="nc" id="L205">        return entryName;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/GeneralUtils$1.html b/testResult/html/stirling.software.SPDF.utils/GeneralUtils$1.html
    new file mode 100644
    index 000000000..e74ce0ae4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/GeneralUtils$1.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GeneralUtils.new SimpleFileVisitor() {...}</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">GeneralUtils.new SimpleFileVisitor() {...}</span></div><h1>GeneralUtils.new SimpleFileVisitor() {...}</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">13 of 13</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">3</td><td class="ctr2">3</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">3</td><td class="ctr2">3</td></tr></tfoot><tbody><tr><td id="a1"><a href="GeneralUtils.java.html#L58" class="el_method">visitFile(Path, BasicFileAttributes)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="5" alt="5"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i0">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="GeneralUtils.java.html#L65" class="el_method">postVisitDirectory(Path, IOException)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="5" alt="5"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="GeneralUtils.java.html#L54" class="el_method">{...}</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="3" alt="3"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/GeneralUtils.html b/testResult/html/stirling.software.SPDF.utils/GeneralUtils.html
    new file mode 100644
    index 000000000..8ca413e59
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/GeneralUtils.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GeneralUtils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">GeneralUtils</span></div><h1>GeneralUtils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">933 of 933</td><td class="ctr2">0%</td><td class="bar">118 of 118</td><td class="ctr2">0%</td><td class="ctr1">84</td><td class="ctr2">84</td><td class="ctr1">207</td><td class="ctr2">207</td><td class="ctr1">25</td><td class="ctr2">25</td></tr></tfoot><tbody><tr><td id="a9"><a href="GeneralUtils.java.html#L387" class="el_method">generateMachineFingerprint()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="125" alt="125"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="84" height="10" title="14" alt="14"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">8</td><td class="ctr2" id="g1">8</td><td class="ctr1" id="h0">26</td><td class="ctr2" id="i0">26</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a11"><a href="GeneralUtils.java.html#L306" class="el_method">handlePart(String, int, int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="109" height="10" title="114" alt="114"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="20" alt="20"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">11</td><td class="ctr2" id="g0">11</td><td class="ctr1" id="h1">24</td><td class="ctr2" id="i1">24</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="GeneralUtils.java.html#L165" class="el_method">convertSizeToBytes(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="95" height="10" title="99" alt="99"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="10" alt="10"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f4">6</td><td class="ctr2" id="g4">6</td><td class="ctr1" id="h2">18</td><td class="ctr2" id="i2">18</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a21"><a href="GeneralUtils.java.html#L227" class="el_method">parsePageList(String[], int, boolean)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="82" height="10" title="86" alt="86"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="12" alt="12"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f2">7</td><td class="ctr2" id="g2">7</td><td class="ctr1" id="h3">13</td><td class="ctr2" id="i3">13</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a17"><a href="GeneralUtils.java.html#L426" class="el_method">isVersionHigher(String, String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="56" alt="56"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="72" height="10" title="12" alt="12"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f3">7</td><td class="ctr2" id="g3">7</td><td class="ctr1" id="h4">13</td><td class="ctr2" id="i4">13</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="GeneralUtils.java.html#L200" class="el_method">formatBytes(long)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="51" height="10" title="54" alt="54"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="6" alt="6"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f8">4</td><td class="ctr2" id="g8">4</td><td class="ctr1" id="h10">7</td><td class="ctr2" id="i10">7</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a5"><a href="GeneralUtils.java.html#L249" class="el_method">evaluateNFunc(String, int)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="51" alt="51"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="10" alt="10"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f5">6</td><td class="ctr2" id="g5">6</td><td class="ctr1" id="h6">11</td><td class="ctr2" id="i6">11</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a14"><a href="GeneralUtils.java.html#L105" class="el_method">isURLReachable(String)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="46" height="10" title="48" alt="48"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="10" alt="10"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f6">6</td><td class="ctr2" id="g6">6</td><td class="ctr1" id="h5">13</td><td class="ctr2" id="i5">13</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a18"><a href="GeneralUtils.java.html#L152" class="el_method">multipartToFile(MultipartFile)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="34" alt="34"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f9">2</td><td class="ctr2" id="g9">2</td><td class="ctr1" id="h11">7</td><td class="ctr2" id="i11">7</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a0"><a href="GeneralUtils.java.html#L37" class="el_method">convertMultipartFileToFile(MultipartFile)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="31" height="10" title="33" alt="33"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f10">2</td><td class="ctr2" id="g10">2</td><td class="ctr1" id="h12">7</td><td class="ctr2" id="i12">7</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a10"><a href="GeneralUtils.java.html#L83" class="el_method">getResourcesFromLocationPattern(String, ResourceLoader)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="32" alt="32"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f11">2</td><td class="ctr2" id="g11">2</td><td class="ctr1" id="h13">6</td><td class="ctr2" id="i13">6</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a22"><a href="GeneralUtils.java.html#L274" class="el_method">sanitizeNFunction(String, int)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="25" alt="25"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">1</td><td class="ctr2" id="g17">1</td><td class="ctr1" id="h7">9</td><td class="ctr2" id="i7">9</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a3"><a href="GeneralUtils.java.html#L348" class="el_method">createDir(String)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="25" alt="25"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f12">2</td><td class="ctr2" id="g12">2</td><td class="ctr1" id="h8">8</td><td class="ctr2" id="i8">8</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a13"><a href="GeneralUtils.java.html#L134" class="el_method">isLocalAddress(String)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="24" alt="24"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="8" alt="8"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f7">5</td><td class="ctr2" id="g7">5</td><td class="ctr1" id="h9">8</td><td class="ctr2" id="i9">8</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a23"><a href="GeneralUtils.java.html#L377" class="el_method">saveKeyToSettings(String, Object)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="24" alt="24"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f18">1</td><td class="ctr2" id="g18">1</td><td class="ctr1" id="h14">6</td><td class="ctr2" id="i14">6</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a19"><a href="GeneralUtils.java.html#L212" class="el_method">parsePageList(String, int, boolean)</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="18" alt="18"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f13">2</td><td class="ctr2" id="g13">2</td><td class="ctr1" id="h16">5</td><td class="ctr2" id="i16">5</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a2"><a href="GeneralUtils.java.html#L72" class="el_method">convertToFileName(String)</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="16" alt="16"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d14"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e14">0%</td><td class="ctr1" id="f14">2</td><td class="ctr2" id="g14">2</td><td class="ctr1" id="h17">4</td><td class="ctr2" id="i17">4</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr><tr><td id="a12"><a href="GeneralUtils.java.html#L291" class="el_method">insertMultiplicationBeforeN(String, int)</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="14" alt="14"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h20">3</td><td class="ctr2" id="i20">3</td><td class="ctr1" id="j17">1</td><td class="ctr2" id="k17">1</td></tr><tr><td id="a7"><a href="GeneralUtils.java.html#L298" class="el_method">formatConsecutiveNsForNFunction(String)</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="14" alt="14"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d15"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e15">0%</td><td class="ctr1" id="f15">2</td><td class="ctr2" id="g15">2</td><td class="ctr1" id="h18">4</td><td class="ctr2" id="i18">4</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k18">1</td></tr><tr><td id="a16"><a href="GeneralUtils.java.html#L361" class="el_method">isValidUUID(String)</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="12" alt="12"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d16"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="2" alt="2"/></td><td class="ctr2" id="e16">0%</td><td class="ctr1" id="f16">2</td><td class="ctr2" id="g16">2</td><td class="ctr1" id="h15">6</td><td class="ctr2" id="i15">6</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td></tr><tr><td id="a15"><a href="GeneralUtils.java.html#L94" class="el_method">isValidURL(String)</a></td><td class="bar" id="b20"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="10" alt="10"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h19">4</td><td class="ctr2" id="i19">4</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td></tr><tr><td id="a4"><a href="GeneralUtils.java.html#L52" class="el_method">deleteDirectory(Path)</a></td><td class="bar" id="b21"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="7" alt="7"/></td><td class="ctr2" id="c21">0%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">1</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">2</td><td class="ctr2" id="i21">2</td><td class="ctr1" id="j21">1</td><td class="ctr2" id="k21">1</td></tr><tr><td id="a20"><a href="GeneralUtils.java.html#L223" class="el_method">parsePageList(String[], int)</a></td><td class="bar" id="b22"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="5" alt="5"/></td><td class="ctr2" id="c22">0%</td><td class="bar" id="d22"/><td class="ctr2" id="e22">n/a</td><td class="ctr1" id="f22">1</td><td class="ctr2" id="g22">1</td><td class="ctr1" id="h22">1</td><td class="ctr2" id="i22">1</td><td class="ctr1" id="j22">1</td><td class="ctr2" id="k22">1</td></tr><tr><td id="a24"><a href="GeneralUtils.java.html#L33" class="el_method">static {...}</a></td><td class="bar" id="b23"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="4" alt="4"/></td><td class="ctr2" id="c23">0%</td><td class="bar" id="d23"/><td class="ctr2" id="e23">n/a</td><td class="ctr1" id="f23">1</td><td class="ctr2" id="g23">1</td><td class="ctr1" id="h23">1</td><td class="ctr2" id="i23">1</td><td class="ctr1" id="j23">1</td><td class="ctr2" id="k23">1</td></tr><tr><td id="a8"><a href="GeneralUtils.java.html#L34" class="el_method">GeneralUtils()</a></td><td class="bar" id="b24"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="3" alt="3"/></td><td class="ctr2" id="c24">0%</td><td class="bar" id="d24"/><td class="ctr2" id="e24">n/a</td><td class="ctr1" id="f24">1</td><td class="ctr2" id="g24">1</td><td class="ctr1" id="h24">1</td><td class="ctr2" id="i24">1</td><td class="ctr1" id="j24">1</td><td class="ctr2" id="k24">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/GeneralUtils.java.html b/testResult/html/stirling.software.SPDF.utils/GeneralUtils.java.html
    new file mode 100644
    index 000000000..e7d085ef9
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/GeneralUtils.java.html
    @@ -0,0 +1,454 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>GeneralUtils.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">GeneralUtils.java</span></div><h1>GeneralUtils.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.io.File;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.*;
    +import java.nio.charset.StandardCharsets;
    +import java.nio.file.*;
    +import java.nio.file.attribute.BasicFileAttributes;
    +import java.security.MessageDigest;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Enumeration;
    +import java.util.List;
    +import java.util.UUID;
    +
    +import org.springframework.core.io.Resource;
    +import org.springframework.core.io.ResourceLoader;
    +import org.springframework.core.io.support.ResourcePatternUtils;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import com.fathzer.soft.javaluator.DoubleEvaluator;
    +
    +import io.github.pixee.security.HostValidator;
    +import io.github.pixee.security.Urls;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.config.YamlHelper;
    +
    +<span class="nc" id="L33">@Slf4j</span>
    +<span class="nc" id="L34">public class GeneralUtils {</span>
    +
    +    public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
    +<span class="nc" id="L37">        File tempFile = Files.createTempFile(&quot;temp&quot;, null).toFile();</span>
    +<span class="nc" id="L38">        try (InputStream inputStream = multipartFile.getInputStream();</span>
    +<span class="nc" id="L39">                FileOutputStream outputStream = new FileOutputStream(tempFile)) {</span>
    +
    +<span class="nc" id="L41">            byte[] buffer = new byte[8192];</span>
    +            int bytesRead;
    +
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">            while ((bytesRead = inputStream.read(buffer)) != -1) {</span>
    +<span class="nc" id="L45">                outputStream.write(buffer, 0, bytesRead);</span>
    +            }
    +        }
    +<span class="nc" id="L48">        return tempFile;</span>
    +    }
    +
    +    public static void deleteDirectory(Path path) throws IOException {
    +<span class="nc" id="L52">        Files.walkFileTree(</span>
    +                path,
    +<span class="nc" id="L54">                new SimpleFileVisitor&lt;Path&gt;() {</span>
    +                    @Override
    +                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
    +                            throws IOException {
    +<span class="nc" id="L58">                        Files.deleteIfExists(file);</span>
    +<span class="nc" id="L59">                        return FileVisitResult.CONTINUE;</span>
    +                    }
    +
    +                    @Override
    +                    public FileVisitResult postVisitDirectory(Path dir, IOException exc)
    +                            throws IOException {
    +<span class="nc" id="L65">                        Files.deleteIfExists(dir);</span>
    +<span class="nc" id="L66">                        return FileVisitResult.CONTINUE;</span>
    +                    }
    +                });
    +<span class="nc" id="L69">    }</span>
    +
    +    public static String convertToFileName(String name) {
    +<span class="nc" id="L72">        String safeName = name.replaceAll(&quot;[^a-zA-Z0-9]&quot;, &quot;_&quot;);</span>
    +<span class="nc bnc" id="L73" title="All 2 branches missed.">        if (safeName.length() &gt; 50) {</span>
    +<span class="nc" id="L74">            safeName = safeName.substring(0, 50);</span>
    +        }
    +<span class="nc" id="L76">        return safeName;</span>
    +    }
    +
    +    // Get resources from a location pattern
    +    public static Resource[] getResourcesFromLocationPattern(
    +            String locationPattern, ResourceLoader resourceLoader) throws Exception {
    +        // Normalize the path for file resources
    +<span class="nc bnc" id="L83" title="All 2 branches missed.">        if (locationPattern.startsWith(&quot;file:&quot;)) {</span>
    +<span class="nc" id="L84">            String rawPath = locationPattern.substring(5).replace(&quot;\\*&quot;, &quot;&quot;).replace(&quot;/*&quot;, &quot;&quot;);</span>
    +<span class="nc" id="L85">            Path normalizePath = Paths.get(rawPath).normalize();</span>
    +<span class="nc" id="L86">            locationPattern = &quot;file:&quot; + normalizePath.toString().replace(&quot;\\&quot;, &quot;/&quot;) + &quot;/*&quot;;</span>
    +        }
    +<span class="nc" id="L88">        return ResourcePatternUtils.getResourcePatternResolver(resourceLoader)</span>
    +<span class="nc" id="L89">                .getResources(locationPattern);</span>
    +    }
    +
    +    public static boolean isValidURL(String urlStr) {
    +        try {
    +<span class="nc" id="L94">            Urls.create(</span>
    +                    urlStr, Urls.HTTP_PROTOCOLS, HostValidator.DENY_COMMON_INFRASTRUCTURE_TARGETS);
    +<span class="nc" id="L96">            return true;</span>
    +<span class="nc" id="L97">        } catch (MalformedURLException e) {</span>
    +<span class="nc" id="L98">            return false;</span>
    +        }
    +    }
    +
    +    public static boolean isURLReachable(String urlStr) {
    +        try {
    +            // Parse the URL
    +<span class="nc" id="L105">            URL url = URI.create(urlStr).toURL();</span>
    +
    +            // Allow only http and https protocols
    +<span class="nc" id="L108">            String protocol = url.getProtocol();</span>
    +<span class="nc bnc" id="L109" title="All 4 branches missed.">            if (!&quot;http&quot;.equals(protocol) &amp;&amp; !&quot;https&quot;.equals(protocol)) {</span>
    +<span class="nc" id="L110">                return false; // Disallow other protocols</span>
    +            }
    +
    +            // Check if the host is a local address
    +<span class="nc" id="L114">            String host = url.getHost();</span>
    +<span class="nc bnc" id="L115" title="All 2 branches missed.">            if (isLocalAddress(host)) {</span>
    +<span class="nc" id="L116">                return false; // Exclude local addresses</span>
    +            }
    +
    +            // Check if the URL is reachable
    +<span class="nc" id="L120">            HttpURLConnection connection = (HttpURLConnection) url.openConnection();</span>
    +<span class="nc" id="L121">            connection.setRequestMethod(&quot;HEAD&quot;);</span>
    +            // connection.setConnectTimeout(5000); // Set connection timeout
    +            // connection.setReadTimeout(5000);    // Set read timeout
    +<span class="nc" id="L124">            int responseCode = connection.getResponseCode();</span>
    +<span class="nc bnc" id="L125" title="All 4 branches missed.">            return (200 &lt;= responseCode &amp;&amp; responseCode &lt;= 399);</span>
    +<span class="nc" id="L126">        } catch (Exception e) {</span>
    +<span class="nc" id="L127">            return false; // Return false in case of any exception</span>
    +        }
    +    }
    +
    +    private static boolean isLocalAddress(String host) {
    +        try {
    +            // Resolve DNS to IP address
    +<span class="nc" id="L134">            InetAddress address = InetAddress.getByName(host);</span>
    +
    +            // Check for local addresses
    +<span class="nc bnc" id="L137" title="All 2 branches missed.">            return address.isAnyLocalAddress()</span>
    +                    || // Matches 0.0.0.0 or similar
    +<span class="nc bnc" id="L139" title="All 2 branches missed.">                    address.isLoopbackAddress()</span>
    +                    || // Matches 127.0.0.1 or ::1
    +<span class="nc bnc" id="L141" title="All 2 branches missed.">                    address.isSiteLocalAddress()</span>
    +                    || // Matches private IPv4 ranges: 192.168.x.x, 10.x.x.x, 172.16.x.x to
    +                    // 172.31.x.x
    +<span class="nc" id="L144">                    address.getHostAddress()</span>
    +<span class="nc bnc" id="L145" title="All 2 branches missed.">                            .startsWith(&quot;fe80:&quot;); // Matches link-local IPv6 addresses</span>
    +<span class="nc" id="L146">        } catch (Exception e) {</span>
    +<span class="nc" id="L147">            return false; // Return false for invalid or unresolved addresses</span>
    +        }
    +    }
    +
    +    public static File multipartToFile(MultipartFile multipart) throws IOException {
    +<span class="nc" id="L152">        Path tempFile = Files.createTempFile(&quot;overlay-&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L153">        try (InputStream in = multipart.getInputStream();</span>
    +<span class="nc" id="L154">                FileOutputStream out = new FileOutputStream(tempFile.toFile())) {</span>
    +<span class="nc" id="L155">            byte[] buffer = new byte[1024];</span>
    +            int bytesRead;
    +<span class="nc bnc" id="L157" title="All 2 branches missed.">            while ((bytesRead = in.read(buffer)) != -1) {</span>
    +<span class="nc" id="L158">                out.write(buffer, 0, bytesRead);</span>
    +            }
    +        }
    +<span class="nc" id="L161">        return tempFile.toFile();</span>
    +    }
    +
    +    public static Long convertSizeToBytes(String sizeStr) {
    +<span class="nc bnc" id="L165" title="All 2 branches missed.">        if (sizeStr == null) {</span>
    +<span class="nc" id="L166">            return null;</span>
    +        }
    +
    +<span class="nc" id="L169">        sizeStr = sizeStr.trim().toUpperCase();</span>
    +<span class="nc" id="L170">        sizeStr = sizeStr.replace(&quot;,&quot;, &quot;.&quot;).replace(&quot; &quot;, &quot;&quot;);</span>
    +        try {
    +<span class="nc bnc" id="L172" title="All 2 branches missed.">            if (sizeStr.endsWith(&quot;KB&quot;)) {</span>
    +<span class="nc" id="L173">                return (long)</span>
    +<span class="nc" id="L174">                        (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2)) * 1024);</span>
    +<span class="nc bnc" id="L175" title="All 2 branches missed.">            } else if (sizeStr.endsWith(&quot;MB&quot;)) {</span>
    +<span class="nc" id="L176">                return (long)</span>
    +<span class="nc" id="L177">                        (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))</span>
    +                                * 1024
    +                                * 1024);
    +<span class="nc bnc" id="L180" title="All 2 branches missed.">            } else if (sizeStr.endsWith(&quot;GB&quot;)) {</span>
    +<span class="nc" id="L181">                return (long)</span>
    +<span class="nc" id="L182">                        (Double.parseDouble(sizeStr.substring(0, sizeStr.length() - 2))</span>
    +                                * 1024
    +                                * 1024
    +                                * 1024);
    +<span class="nc bnc" id="L186" title="All 2 branches missed.">            } else if (sizeStr.endsWith(&quot;B&quot;)) {</span>
    +<span class="nc" id="L187">                return Long.parseLong(sizeStr.substring(0, sizeStr.length() - 1));</span>
    +            } else {
    +                // Assume MB if no unit is specified
    +<span class="nc" id="L190">                return (long) (Double.parseDouble(sizeStr) * 1024 * 1024);</span>
    +            }
    +<span class="nc" id="L192">        } catch (NumberFormatException e) {</span>
    +            // The numeric part of the input string cannot be parsed, handle this case
    +        }
    +
    +<span class="nc" id="L196">        return null;</span>
    +    }
    +
    +    public static String formatBytes(long bytes) {
    +<span class="nc bnc" id="L200" title="All 2 branches missed.">        if (bytes &lt; 1024) {</span>
    +<span class="nc" id="L201">            return bytes + &quot; B&quot;;</span>
    +<span class="nc bnc" id="L202" title="All 2 branches missed.">        } else if (bytes &lt; 1024 * 1024) {</span>
    +<span class="nc" id="L203">            return String.format(&quot;%.2f KB&quot;, bytes / 1024.0);</span>
    +<span class="nc bnc" id="L204" title="All 2 branches missed.">        } else if (bytes &lt; 1024 * 1024 * 1024) {</span>
    +<span class="nc" id="L205">            return String.format(&quot;%.2f MB&quot;, bytes / (1024.0 * 1024.0));</span>
    +        } else {
    +<span class="nc" id="L207">            return String.format(&quot;%.2f GB&quot;, bytes / (1024.0 * 1024.0 * 1024.0));</span>
    +        }
    +    }
    +
    +    public static List&lt;Integer&gt; parsePageList(String pages, int totalPages, boolean oneBased) {
    +<span class="nc bnc" id="L212" title="All 2 branches missed.">        if (pages == null) {</span>
    +<span class="nc" id="L213">            return List.of(1); // Default to first page if input is null</span>
    +        }
    +        try {
    +<span class="nc" id="L216">            return parsePageList(pages.split(&quot;,&quot;), totalPages, oneBased);</span>
    +<span class="nc" id="L217">        } catch (NumberFormatException e) {</span>
    +<span class="nc" id="L218">            return List.of(1); // Default to first page if input is invalid</span>
    +        }
    +    }
    +
    +    public static List&lt;Integer&gt; parsePageList(String[] pages, int totalPages) {
    +<span class="nc" id="L223">        return parsePageList(pages, totalPages, false);</span>
    +    }
    +
    +    public static List&lt;Integer&gt; parsePageList(String[] pages, int totalPages, boolean oneBased) {
    +<span class="nc" id="L227">        List&lt;Integer&gt; result = new ArrayList&lt;&gt;();</span>
    +<span class="nc bnc" id="L228" title="All 2 branches missed.">        int offset = oneBased ? 1 : 0;</span>
    +<span class="nc bnc" id="L229" title="All 2 branches missed.">        for (String page : pages) {</span>
    +<span class="nc bnc" id="L230" title="All 2 branches missed.">            if (&quot;all&quot;.equalsIgnoreCase(page)) {</span>
    +
    +<span class="nc bnc" id="L232" title="All 2 branches missed.">                for (int i = 0; i &lt; totalPages; i++) {</span>
    +<span class="nc" id="L233">                    result.add(i + offset);</span>
    +                }
    +<span class="nc bnc" id="L235" title="All 2 branches missed.">            } else if (page.contains(&quot;,&quot;)) {</span>
    +                // Split the string into parts, could be single pages or ranges
    +<span class="nc" id="L237">                String[] parts = page.split(&quot;,&quot;);</span>
    +<span class="nc bnc" id="L238" title="All 2 branches missed.">                for (String part : parts) {</span>
    +<span class="nc" id="L239">                    result.addAll(handlePart(part, totalPages, offset));</span>
    +                }
    +<span class="nc" id="L241">            } else {</span>
    +<span class="nc" id="L242">                result.addAll(handlePart(page, totalPages, offset));</span>
    +            }
    +        }
    +<span class="nc" id="L245">        return result;</span>
    +    }
    +
    +    public static List&lt;Integer&gt; evaluateNFunc(String expression, int maxValue) {
    +<span class="nc" id="L249">        List&lt;Integer&gt; results = new ArrayList&lt;&gt;();</span>
    +<span class="nc" id="L250">        DoubleEvaluator evaluator = new DoubleEvaluator();</span>
    +
    +        // Validate the expression
    +<span class="nc bnc" id="L253" title="All 2 branches missed.">        if (!expression.matches(&quot;[0-9n+\\-*/() ]+&quot;)) {</span>
    +<span class="nc" id="L254">            throw new IllegalArgumentException(&quot;Invalid expression&quot;);</span>
    +        }
    +
    +<span class="nc bnc" id="L257" title="All 2 branches missed.">        for (int n = 1; n &lt;= maxValue; n++) {</span>
    +            // Replace 'n' with the current value of n, correctly handling numbers before
    +            // 'n'
    +<span class="nc" id="L260">            String sanitizedExpression = sanitizeNFunction(expression, n);</span>
    +<span class="nc" id="L261">            Double result = evaluator.evaluate(sanitizedExpression);</span>
    +
    +            // Check if the result is null or not within bounds
    +<span class="nc bnc" id="L264" title="All 2 branches missed.">            if (result == null) break;</span>
    +
    +<span class="nc bnc" id="L266" title="All 4 branches missed.">            if (result.intValue() &gt; 0 &amp;&amp; result.intValue() &lt;= maxValue)</span>
    +<span class="nc" id="L267">                results.add(result.intValue());</span>
    +        }
    +
    +<span class="nc" id="L270">        return results;</span>
    +    }
    +
    +    private static String sanitizeNFunction(String expression, int nValue) {
    +<span class="nc" id="L274">        String sanitizedExpression = expression.replace(&quot; &quot;, &quot;&quot;);</span>
    +<span class="nc" id="L275">        String multiplyByOpeningRoundBracketPattern =</span>
    +                &quot;([0-9n)])\\(&quot;; // example: n(n-1), 9(n-1), (n-1)(n-2)
    +<span class="nc" id="L277">        sanitizedExpression =</span>
    +<span class="nc" id="L278">                sanitizedExpression.replaceAll(multiplyByOpeningRoundBracketPattern, &quot;$1*(&quot;);</span>
    +
    +<span class="nc" id="L280">        String multiplyByClosingRoundBracketPattern =</span>
    +                &quot;\\)([0-9n)])&quot;; // example: (n-1)n, (n-1)9, (n-1)(n-2)
    +<span class="nc" id="L282">        sanitizedExpression =</span>
    +<span class="nc" id="L283">                sanitizedExpression.replaceAll(multiplyByClosingRoundBracketPattern, &quot;)*$1&quot;);</span>
    +
    +<span class="nc" id="L285">        sanitizedExpression = insertMultiplicationBeforeN(sanitizedExpression, nValue);</span>
    +<span class="nc" id="L286">        return sanitizedExpression;</span>
    +    }
    +
    +    private static String insertMultiplicationBeforeN(String expression, int nValue) {
    +        // Insert multiplication between a number and 'n' (e.g., &quot;4n&quot; becomes &quot;4*n&quot;)
    +<span class="nc" id="L291">        String withMultiplication = expression.replaceAll(&quot;(\\d)n&quot;, &quot;$1*n&quot;);</span>
    +<span class="nc" id="L292">        withMultiplication = formatConsecutiveNsForNFunction(withMultiplication);</span>
    +        // Now replace 'n' with its current value
    +<span class="nc" id="L294">        return withMultiplication.replace(&quot;n&quot;, String.valueOf(nValue));</span>
    +    }
    +
    +    private static String formatConsecutiveNsForNFunction(String expression) {
    +<span class="nc" id="L298">        String text = expression;</span>
    +<span class="nc bnc" id="L299" title="All 2 branches missed.">        while (text.matches(&quot;.*n{2,}.*&quot;)) {</span>
    +<span class="nc" id="L300">            text = text.replaceAll(&quot;(?&lt;!n)n{2}&quot;, &quot;n*n&quot;);</span>
    +        }
    +<span class="nc" id="L302">        return text;</span>
    +    }
    +
    +    private static List&lt;Integer&gt; handlePart(String part, int totalPages, int offset) {
    +<span class="nc" id="L306">        List&lt;Integer&gt; partResult = new ArrayList&lt;&gt;();</span>
    +
    +        // First check for n-syntax because it should not be processed as a range
    +<span class="nc bnc" id="L309" title="All 2 branches missed.">        if (part.contains(&quot;n&quot;)) {</span>
    +<span class="nc" id="L310">            partResult = evaluateNFunc(part, totalPages);</span>
    +            // Adjust the results according to the offset
    +<span class="nc bnc" id="L312" title="All 2 branches missed.">            for (int i = 0; i &lt; partResult.size(); i++) {</span>
    +<span class="nc" id="L313">                int adjustedValue = partResult.get(i) - 1 + offset;</span>
    +<span class="nc" id="L314">                partResult.set(i, adjustedValue);</span>
    +            }
    +<span class="nc bnc" id="L316" title="All 2 branches missed.">        } else if (part.contains(&quot;-&quot;)) {</span>
    +            // Process ranges only if it's not n-syntax
    +<span class="nc" id="L318">            String[] rangeParts = part.split(&quot;-&quot;);</span>
    +            try {
    +<span class="nc" id="L320">                int start = Integer.parseInt(rangeParts[0]);</span>
    +                int end =
    +<span class="nc bnc" id="L322" title="All 4 branches missed.">                        (rangeParts.length &gt; 1 &amp;&amp; !rangeParts[1].isEmpty())</span>
    +<span class="nc" id="L323">                                ? Integer.parseInt(rangeParts[1])</span>
    +<span class="nc" id="L324">                                : totalPages;</span>
    +<span class="nc bnc" id="L325" title="All 2 branches missed.">                for (int i = start; i &lt;= end; i++) {</span>
    +<span class="nc bnc" id="L326" title="All 4 branches missed.">                    if (i &gt;= 1 &amp;&amp; i &lt;= totalPages) {</span>
    +<span class="nc" id="L327">                        partResult.add(i - 1 + offset);</span>
    +                    }
    +                }
    +<span class="nc" id="L330">            } catch (NumberFormatException e) {</span>
    +                // Range is invalid, ignore this part
    +<span class="nc" id="L332">            }</span>
    +<span class="nc" id="L333">        } else {</span>
    +            // This is a single page number
    +            try {
    +<span class="nc" id="L336">                int pageNum = Integer.parseInt(part.trim());</span>
    +<span class="nc bnc" id="L337" title="All 4 branches missed.">                if (pageNum &gt;= 1 &amp;&amp; pageNum &lt;= totalPages) {</span>
    +<span class="nc" id="L338">                    partResult.add(pageNum - 1 + offset);</span>
    +                }
    +<span class="nc" id="L340">            } catch (NumberFormatException ignored) {</span>
    +                // Ignore invalid numbers
    +<span class="nc" id="L342">            }</span>
    +        }
    +<span class="nc" id="L344">        return partResult;</span>
    +    }
    +
    +    public static boolean createDir(String path) {
    +<span class="nc" id="L348">        Path folder = Paths.get(path);</span>
    +<span class="nc bnc" id="L349" title="All 2 branches missed.">        if (!Files.exists(folder)) {</span>
    +            try {
    +<span class="nc" id="L351">                Files.createDirectories(folder);</span>
    +<span class="nc" id="L352">            } catch (IOException e) {</span>
    +<span class="nc" id="L353">                log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L354">                return false;</span>
    +<span class="nc" id="L355">            }</span>
    +        }
    +<span class="nc" id="L357">        return true;</span>
    +    }
    +
    +    public static boolean isValidUUID(String uuid) {
    +<span class="nc bnc" id="L361" title="All 2 branches missed.">        if (uuid == null) {</span>
    +<span class="nc" id="L362">            return false;</span>
    +        }
    +        try {
    +<span class="nc" id="L365">            UUID.fromString(uuid);</span>
    +<span class="nc" id="L366">            return true;</span>
    +<span class="nc" id="L367">        } catch (IllegalArgumentException e) {</span>
    +<span class="nc" id="L368">            return false;</span>
    +        }
    +    }
    +
    +    /*------------------------------------------------------------------------*
    +     *                  Internal Implementation Details                       *
    +     *------------------------------------------------------------------------*/
    +
    +    public static void saveKeyToSettings(String key, Object newValue) throws IOException {
    +<span class="nc" id="L377">        String[] keyArray = key.split(&quot;\\.&quot;);</span>
    +<span class="nc" id="L378">        Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());</span>
    +<span class="nc" id="L379">        YamlHelper settingsYaml = new YamlHelper(settingsPath);</span>
    +<span class="nc" id="L380">        settingsYaml.updateValue(Arrays.asList(keyArray), newValue);</span>
    +<span class="nc" id="L381">        settingsYaml.saveOverride(settingsPath);</span>
    +<span class="nc" id="L382">    }</span>
    +
    +    public static String generateMachineFingerprint() {
    +        try {
    +            // Get the MAC address
    +<span class="nc" id="L387">            StringBuilder sb = new StringBuilder();</span>
    +<span class="nc" id="L388">            InetAddress ip = InetAddress.getLocalHost();</span>
    +<span class="nc" id="L389">            NetworkInterface network = NetworkInterface.getByInetAddress(ip);</span>
    +
    +<span class="nc bnc" id="L391" title="All 2 branches missed.">            if (network == null) {</span>
    +<span class="nc" id="L392">                Enumeration&lt;NetworkInterface&gt; networks = NetworkInterface.getNetworkInterfaces();</span>
    +<span class="nc bnc" id="L393" title="All 2 branches missed.">                while (networks.hasMoreElements()) {</span>
    +<span class="nc" id="L394">                    NetworkInterface net = networks.nextElement();</span>
    +<span class="nc" id="L395">                    byte[] mac = net.getHardwareAddress();</span>
    +<span class="nc bnc" id="L396" title="All 2 branches missed.">                    if (mac != null) {</span>
    +<span class="nc bnc" id="L397" title="All 2 branches missed.">                        for (int i = 0; i &lt; mac.length; i++) {</span>
    +<span class="nc" id="L398">                            sb.append(String.format(&quot;%02X&quot;, mac[i]));</span>
    +                        }
    +<span class="nc" id="L400">                        break; // Use the first network interface with a MAC address</span>
    +                    }
    +<span class="nc" id="L402">                }</span>
    +<span class="nc" id="L403">            } else {</span>
    +<span class="nc" id="L404">                byte[] mac = network.getHardwareAddress();</span>
    +<span class="nc bnc" id="L405" title="All 2 branches missed.">                if (mac != null) {</span>
    +<span class="nc bnc" id="L406" title="All 2 branches missed.">                    for (int i = 0; i &lt; mac.length; i++) {</span>
    +<span class="nc" id="L407">                        sb.append(String.format(&quot;%02X&quot;, mac[i]));</span>
    +                    }
    +                }
    +            }
    +
    +            // Hash the MAC address for privacy and consistency
    +<span class="nc" id="L413">            MessageDigest md = MessageDigest.getInstance(&quot;SHA-256&quot;);</span>
    +<span class="nc" id="L414">            byte[] hash = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));</span>
    +<span class="nc" id="L415">            StringBuilder fingerprint = new StringBuilder();</span>
    +<span class="nc bnc" id="L416" title="All 2 branches missed.">            for (byte b : hash) {</span>
    +<span class="nc" id="L417">                fingerprint.append(String.format(&quot;%02x&quot;, b));</span>
    +            }
    +<span class="nc" id="L419">            return fingerprint.toString();</span>
    +<span class="nc" id="L420">        } catch (Exception e) {</span>
    +<span class="nc" id="L421">            return &quot;GenericID&quot;;</span>
    +        }
    +    }
    +
    +    public static boolean isVersionHigher(String currentVersion, String compareVersion) {
    +<span class="nc bnc" id="L426" title="All 4 branches missed.">        if (currentVersion == null || compareVersion == null) {</span>
    +<span class="nc" id="L427">            return false;</span>
    +        }
    +
    +        // Split versions into components
    +<span class="nc" id="L431">        String[] current = currentVersion.split(&quot;\\.&quot;);</span>
    +<span class="nc" id="L432">        String[] compare = compareVersion.split(&quot;\\.&quot;);</span>
    +
    +        // Get the length of the shorter version array
    +<span class="nc" id="L435">        int length = Math.min(current.length, compare.length);</span>
    +
    +        // Compare each component
    +<span class="nc bnc" id="L438" title="All 2 branches missed.">        for (int i = 0; i &lt; length; i++) {</span>
    +<span class="nc" id="L439">            int currentPart = Integer.parseInt(current[i]);</span>
    +<span class="nc" id="L440">            int comparePart = Integer.parseInt(compare[i]);</span>
    +
    +<span class="nc bnc" id="L442" title="All 2 branches missed.">            if (currentPart &gt; comparePart) {</span>
    +<span class="nc" id="L443">                return true;</span>
    +            }
    +<span class="nc bnc" id="L445" title="All 2 branches missed.">            if (currentPart &lt; comparePart) {</span>
    +<span class="nc" id="L446">                return false;</span>
    +            }
    +        }
    +
    +        // If all components so far are equal, the longer version is considered higher
    +<span class="nc bnc" id="L451" title="All 2 branches missed.">        return current.length &gt; compare.length;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/ImageProcessingUtils.html b/testResult/html/stirling.software.SPDF.utils/ImageProcessingUtils.html
    new file mode 100644
    index 000000000..e6f7cb6d4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/ImageProcessingUtils.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ImageProcessingUtils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">ImageProcessingUtils</span></div><h1>ImageProcessingUtils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">234 of 234</td><td class="ctr2">0%</td><td class="bar">20 of 20</td><td class="ctr2">0%</td><td class="ctr1">19</td><td class="ctr2">19</td><td class="ctr1">61</td><td class="ctr2">61</td><td class="ctr1">7</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a3"><a href="ImageProcessingUtils.java.html#L51" class="el_method">getImageData(BufferedImage)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="100" alt="100"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">5</td><td class="ctr2" id="g1">5</td><td class="ctr1" id="h0">19</td><td class="ctr2" id="i0">19</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="ImageProcessingUtils.java.html#L26" class="el_method">convertColorType(BufferedImage, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="57" height="10" title="48" alt="48"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="3" alt="3"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h2">13</td><td class="ctr2" id="i2">13</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="ImageProcessingUtils.java.html#L78" class="el_method">extractImageOrientation(InputStream)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="43" height="10" title="36" alt="36"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="105" height="10" title="7" alt="7"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f0">6</td><td class="ctr2" id="g0">6</td><td class="ctr1" id="h1">15</td><td class="ctr2" id="i1">15</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="ImageProcessingUtils.java.html#L104" class="el_method">applyOrientation(BufferedImage, double)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="37" height="10" title="31" alt="31"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h3">9</td><td class="ctr2" id="i3">9</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a5"><a href="ImageProcessingUtils.java.html#L118" class="el_method">loadImageWithExifOrientation(MultipartFile)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="12" alt="12"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">3</td><td class="ctr2" id="i4">3</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a6"><a href="ImageProcessingUtils.java.html#L21" class="el_method">static {...}</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="ImageProcessingUtils.java.html#L22" class="el_method">ImageProcessingUtils()</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/ImageProcessingUtils.java.html b/testResult/html/stirling.software.SPDF.utils/ImageProcessingUtils.java.html
    new file mode 100644
    index 000000000..539ca1db2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/ImageProcessingUtils.java.html
    @@ -0,0 +1,123 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ImageProcessingUtils.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">ImageProcessingUtils.java</span></div><h1>ImageProcessingUtils.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.awt.geom.AffineTransform;
    +import java.awt.image.*;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.nio.ByteBuffer;
    +
    +import javax.imageio.ImageIO;
    +
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import com.drew.imaging.ImageMetadataReader;
    +import com.drew.imaging.ImageProcessingException;
    +import com.drew.metadata.Metadata;
    +import com.drew.metadata.MetadataException;
    +import com.drew.metadata.exif.ExifSubIFDDirectory;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +<span class="nc" id="L21">@Slf4j</span>
    +<span class="nc" id="L22">public class ImageProcessingUtils {</span>
    +
    +    static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) {
    +        BufferedImage convertedImage;
    +<span class="nc bnc" id="L26" title="All 3 branches missed.">        switch (colorType) {</span>
    +            case &quot;greyscale&quot;:
    +<span class="nc" id="L28">                convertedImage =</span>
    +                        new BufferedImage(
    +<span class="nc" id="L30">                                sourceImage.getWidth(),</span>
    +<span class="nc" id="L31">                                sourceImage.getHeight(),</span>
    +                                BufferedImage.TYPE_BYTE_GRAY);
    +<span class="nc" id="L33">                convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);</span>
    +<span class="nc" id="L34">                break;</span>
    +            case &quot;blackwhite&quot;:
    +<span class="nc" id="L36">                convertedImage =</span>
    +                        new BufferedImage(
    +<span class="nc" id="L38">                                sourceImage.getWidth(),</span>
    +<span class="nc" id="L39">                                sourceImage.getHeight(),</span>
    +                                BufferedImage.TYPE_BYTE_BINARY);
    +<span class="nc" id="L41">                convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);</span>
    +<span class="nc" id="L42">                break;</span>
    +            default: // full color
    +<span class="nc" id="L44">                convertedImage = sourceImage;</span>
    +                break;
    +        }
    +<span class="nc" id="L47">        return convertedImage;</span>
    +    }
    +
    +    public static byte[] getImageData(BufferedImage image) {
    +<span class="nc" id="L51">        DataBuffer dataBuffer = image.getRaster().getDataBuffer();</span>
    +<span class="nc bnc" id="L52" title="All 2 branches missed.">        if (dataBuffer instanceof DataBufferByte dataBufferByte) {</span>
    +<span class="nc" id="L53">            return dataBufferByte.getData();</span>
    +<span class="nc bnc" id="L54" title="All 2 branches missed.">        } else if (dataBuffer instanceof DataBufferInt dataBufferInt) {</span>
    +<span class="nc" id="L55">            int[] intData = dataBufferInt.getData();</span>
    +<span class="nc" id="L56">            ByteBuffer byteBuffer = ByteBuffer.allocate(intData.length * 4);</span>
    +<span class="nc" id="L57">            byteBuffer.asIntBuffer().put(intData);</span>
    +<span class="nc" id="L58">            return byteBuffer.array();</span>
    +        } else {
    +<span class="nc" id="L60">            int width = image.getWidth();</span>
    +<span class="nc" id="L61">            int height = image.getHeight();</span>
    +<span class="nc" id="L62">            byte[] data = new byte[width * height * 3];</span>
    +<span class="nc" id="L63">            int index = 0;</span>
    +<span class="nc bnc" id="L64" title="All 2 branches missed.">            for (int y = 0; y &lt; height; y++) {</span>
    +<span class="nc bnc" id="L65" title="All 2 branches missed.">                for (int x = 0; x &lt; width; x++) {</span>
    +<span class="nc" id="L66">                    int rgb = image.getRGB(x, y);</span>
    +<span class="nc" id="L67">                    data[index++] = (byte) ((rgb &gt;&gt; 16) &amp; 0xFF); // Red</span>
    +<span class="nc" id="L68">                    data[index++] = (byte) ((rgb &gt;&gt; 8) &amp; 0xFF); // Green</span>
    +<span class="nc" id="L69">                    data[index++] = (byte) (rgb &amp; 0xFF); // Blue</span>
    +                }
    +            }
    +<span class="nc" id="L72">            return data;</span>
    +        }
    +    }
    +
    +    public static double extractImageOrientation(InputStream is) throws IOException {
    +        try {
    +<span class="nc" id="L78">            Metadata metadata = ImageMetadataReader.readMetadata(is);</span>
    +<span class="nc" id="L79">            ExifSubIFDDirectory directory =</span>
    +<span class="nc" id="L80">                    metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);</span>
    +<span class="nc bnc" id="L81" title="All 2 branches missed.">            if (directory == null) {</span>
    +<span class="nc" id="L82">                return 0;</span>
    +            }
    +<span class="nc" id="L84">            int orientationTag = directory.getInt(ExifSubIFDDirectory.TAG_ORIENTATION);</span>
    +<span class="nc bnc" id="L85" title="All 5 branches missed.">            switch (orientationTag) {</span>
    +                case 1:
    +<span class="nc" id="L87">                    return 0;</span>
    +                case 6:
    +<span class="nc" id="L89">                    return 90;</span>
    +                case 3:
    +<span class="nc" id="L91">                    return 180;</span>
    +                case 8:
    +<span class="nc" id="L93">                    return 270;</span>
    +                default:
    +<span class="nc" id="L95">                    log.warn(&quot;Unknown orientation tag: {}&quot;, orientationTag);</span>
    +<span class="nc" id="L96">                    return 0;</span>
    +            }
    +<span class="nc" id="L98">        } catch (ImageProcessingException | MetadataException e) {</span>
    +<span class="nc" id="L99">            return 0;</span>
    +        }
    +    }
    +
    +    public static BufferedImage applyOrientation(BufferedImage image, double orientation) {
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">        if (orientation == 0) {</span>
    +<span class="nc" id="L105">            return image;</span>
    +        }
    +<span class="nc" id="L107">        AffineTransform transform =</span>
    +<span class="nc" id="L108">                AffineTransform.getRotateInstance(</span>
    +<span class="nc" id="L109">                        Math.toRadians(orientation),</span>
    +<span class="nc" id="L110">                        image.getWidth() / 2.0,</span>
    +<span class="nc" id="L111">                        image.getHeight() / 2.0);</span>
    +<span class="nc" id="L112">        AffineTransformOp op = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);</span>
    +<span class="nc" id="L113">        return op.filter(image, null);</span>
    +    }
    +
    +    public static BufferedImage loadImageWithExifOrientation(MultipartFile file)
    +            throws IOException {
    +<span class="nc" id="L118">        BufferedImage image = ImageIO.read(file.getInputStream());</span>
    +<span class="nc" id="L119">        double orientation = extractImageOrientation(file.getInputStream());</span>
    +<span class="nc" id="L120">        return applyOrientation(image, orientation);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/PDFToFile.html b/testResult/html/stirling.software.SPDF.utils/PDFToFile.html
    new file mode 100644
    index 000000000..b0fd4860b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/PDFToFile.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PDFToFile</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">PDFToFile</span></div><h1>PDFToFile</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">502 of 723</td><td class="ctr2">30%</td><td class="bar">40 of 50</td><td class="ctr2">20%</td><td class="ctr1">24</td><td class="ctr2">30</td><td class="ctr1">107</td><td class="ctr2">159</td><td class="ctr1">2</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a3"><a href="PDFToFile.java.html#L224" class="el_method">processPdfToOfficeFormat(MultipartFile, String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="103" height="10" title="256" alt="256"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="98" height="10" title="18" alt="18"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">10</td><td class="ctr2" id="g1">10</td><td class="ctr1" id="h0">53</td><td class="ctr2" id="i1">53</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="PDFToFile.java.html#L154" class="el_method">processPdfToHtml(MultipartFile)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="65" height="10" title="162" alt="162"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="10" alt="10"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">6</td><td class="ctr2" id="g2">6</td><td class="ctr1" id="h1">38</td><td class="ctr2" id="i2">38</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="PDFToFile.java.html#L37" class="el_method">processPdfToMarkdown(MultipartFile)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="84" alt="84"/><img src="../jacoco-resources/greenbar.gif" width="86" height="10" title="214" alt="214"/></td><td class="ctr2" id="c2">71%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="65" height="10" title="12" alt="12"/><img src="../jacoco-resources/greenbar.gif" width="54" height="10" title="10" alt="10"/></td><td class="ctr2" id="e0">45%</td><td class="ctr1" id="f1">8</td><td class="ctr2" id="g0">12</td><td class="ctr1" id="h2">16</td><td class="ctr2" id="i0">66</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a4"><a href="PDFToFile.java.html#L32" class="el_method">static {...}</a></td><td class="bar" id="b3"><img src="../jacoco-resources/greenbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">0</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">0</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">0</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a0"><a href="PDFToFile.java.html#L33" class="el_method">PDFToFile()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/greenbar.gif" width="1" height="10" title="3" alt="3"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">0</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">0</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">0</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/PDFToFile.java.html b/testResult/html/stirling.software.SPDF.utils/PDFToFile.java.html
    new file mode 100644
    index 000000000..b4ff85ac8
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/PDFToFile.java.html
    @@ -0,0 +1,319 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PDFToFile.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">PDFToFile.java</span></div><h1>PDFToFile.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.File;
    +import java.io.FileInputStream;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.List;
    +import java.util.Objects;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import org.apache.commons.io.FileUtils;
    +import org.apache.commons.io.IOUtils;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter;
    +import com.vladsch.flexmark.util.data.MutableDataSet;
    +
    +import io.github.pixee.security.Filenames;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
    +
    +<span class="fc" id="L32">@Slf4j</span>
    +<span class="fc" id="L33">public class PDFToFile {</span>
    +
    +    public ResponseEntity&lt;byte[]&gt; processPdfToMarkdown(MultipartFile inputFile)
    +            throws IOException, InterruptedException {
    +<span class="fc bfc" id="L37" title="All 2 branches covered.">        if (!&quot;application/pdf&quot;.equals(inputFile.getContentType())) {</span>
    +<span class="fc" id="L38">            return new ResponseEntity&lt;&gt;(HttpStatus.BAD_REQUEST);</span>
    +        }
    +
    +<span class="fc" id="L41">        MutableDataSet options =</span>
    +                new MutableDataSet()
    +<span class="fc" id="L43">                        .set(</span>
    +                                FlexmarkHtmlConverter.MAX_BLANK_LINES,
    +<span class="fc" id="L45">                                2) // Control max consecutive blank lines</span>
    +<span class="fc" id="L46">                        .set(</span>
    +                                FlexmarkHtmlConverter.MAX_TRAILING_BLANK_LINES,
    +<span class="fc" id="L48">                                1) // Control trailing blank lines</span>
    +<span class="fc" id="L49">                        .set(</span>
    +                                FlexmarkHtmlConverter.SETEXT_HEADINGS,
    +<span class="fc" id="L51">                                true) // Use Setext headings for h1 and h2</span>
    +<span class="fc" id="L52">                        .set(</span>
    +                                FlexmarkHtmlConverter.OUTPUT_UNKNOWN_TAGS,
    +<span class="fc" id="L54">                                false) // Don't output HTML for unknown tags</span>
    +<span class="fc" id="L55">                        .set(</span>
    +                                FlexmarkHtmlConverter.TYPOGRAPHIC_QUOTES,
    +<span class="fc" id="L57">                                true) // Convert quotation marks</span>
    +<span class="fc" id="L58">                        .set(</span>
    +                                FlexmarkHtmlConverter.BR_AS_PARA_BREAKS,
    +<span class="fc" id="L60">                                true) // Convert &lt;br&gt; to paragraph breaks</span>
    +<span class="fc" id="L61">                        .set(FlexmarkHtmlConverter.CODE_INDENT, &quot;    &quot;); // Indent for code blocks</span>
    +
    +<span class="fc" id="L63">        FlexmarkHtmlConverter htmlToMarkdownConverter =</span>
    +<span class="fc" id="L64">                FlexmarkHtmlConverter.builder(options).build();</span>
    +
    +<span class="fc" id="L66">        String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());</span>
    +<span class="fc" id="L67">        String pdfBaseName = originalPdfFileName;</span>
    +<span class="pc bpc" id="L68" title="1 of 2 branches missed.">        if (originalPdfFileName.contains(&quot;.&quot;)) {</span>
    +<span class="fc" id="L69">            pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));</span>
    +        }
    +
    +<span class="fc" id="L72">        Path tempInputFile = null;</span>
    +<span class="fc" id="L73">        Path tempOutputDir = null;</span>
    +        byte[] fileBytes;
    +<span class="fc" id="L75">        String fileName = &quot;temp.file&quot;;</span>
    +
    +        try {
    +<span class="fc" id="L78">            tempInputFile = Files.createTempFile(&quot;input_&quot;, &quot;.pdf&quot;);</span>
    +<span class="fc" id="L79">            inputFile.transferTo(tempInputFile);</span>
    +<span class="fc" id="L80">            tempOutputDir = Files.createTempDirectory(&quot;output_&quot;);</span>
    +
    +<span class="fc" id="L82">            List&lt;String&gt; command =</span>
    +                    new ArrayList&lt;&gt;(
    +<span class="fc" id="L84">                            Arrays.asList(</span>
    +                                    &quot;pdftohtml&quot;,
    +                                    &quot;-s&quot;,
    +                                    &quot;-noframes&quot;,
    +                                    &quot;-c&quot;,
    +<span class="fc" id="L89">                                    tempInputFile.toString(),</span>
    +                                    pdfBaseName));
    +
    +<span class="fc" id="L92">            ProcessExecutorResult returnCode =</span>
    +<span class="fc" id="L93">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)</span>
    +<span class="fc" id="L94">                            .runCommandWithOutputHandling(command, tempOutputDir.toFile());</span>
    +            // Process HTML files to Markdown
    +<span class="fc" id="L96">            File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles());</span>
    +<span class="fc" id="L97">            List&lt;File&gt; markdownFiles = new ArrayList&lt;&gt;();</span>
    +
    +            // Convert HTML files to Markdown
    +<span class="fc bfc" id="L100" title="All 2 branches covered.">            for (File outputFile : outputFiles) {</span>
    +<span class="fc bfc" id="L101" title="All 2 branches covered.">                if (outputFile.getName().endsWith(&quot;.html&quot;)) {</span>
    +<span class="fc" id="L102">                    String html = Files.readString(outputFile.toPath());</span>
    +<span class="fc" id="L103">                    String markdown = htmlToMarkdownConverter.convert(html);</span>
    +
    +<span class="fc" id="L105">                    String mdFileName = outputFile.getName().replace(&quot;.html&quot;, &quot;.md&quot;);</span>
    +<span class="fc" id="L106">                    File mdFile = new File(tempOutputDir.toFile(), mdFileName);</span>
    +<span class="fc" id="L107">                    Files.writeString(mdFile.toPath(), markdown);</span>
    +<span class="fc" id="L108">                    markdownFiles.add(mdFile);</span>
    +                }
    +            }
    +
    +            // If there's only one markdown file, return it directly
    +<span class="pc bpc" id="L113" title="1 of 2 branches missed.">            if (markdownFiles.size() == 1) {</span>
    +<span class="fc" id="L114">                fileName = pdfBaseName + &quot;.md&quot;;</span>
    +<span class="fc" id="L115">                fileBytes = Files.readAllBytes(markdownFiles.get(0).toPath());</span>
    +            } else {
    +                // Multiple files - create a zip
    +<span class="nc" id="L118">                fileName = pdfBaseName + &quot;ToMarkdown.zip&quot;;</span>
    +<span class="nc" id="L119">                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();</span>
    +
    +<span class="nc" id="L121">                try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {</span>
    +                    // Add markdown files
    +<span class="nc bnc" id="L123" title="All 2 branches missed.">                    for (File mdFile : markdownFiles) {</span>
    +<span class="nc" id="L124">                        ZipEntry mdEntry = new ZipEntry(mdFile.getName());</span>
    +<span class="nc" id="L125">                        zipOutputStream.putNextEntry(mdEntry);</span>
    +<span class="nc" id="L126">                        Files.copy(mdFile.toPath(), zipOutputStream);</span>
    +<span class="nc" id="L127">                        zipOutputStream.closeEntry();</span>
    +<span class="nc" id="L128">                    }</span>
    +
    +                    // Add images and other assets
    +<span class="nc bnc" id="L131" title="All 2 branches missed.">                    for (File file : outputFiles) {</span>
    +<span class="nc bnc" id="L132" title="All 4 branches missed.">                        if (!file.getName().endsWith(&quot;.html&quot;) &amp;&amp; !file.getName().endsWith(&quot;.md&quot;)) {</span>
    +<span class="nc" id="L133">                            ZipEntry assetEntry = new ZipEntry(file.getName());</span>
    +<span class="nc" id="L134">                            zipOutputStream.putNextEntry(assetEntry);</span>
    +<span class="nc" id="L135">                            Files.copy(file.toPath(), zipOutputStream);</span>
    +<span class="nc" id="L136">                            zipOutputStream.closeEntry();</span>
    +                        }
    +                    }
    +                }
    +
    +<span class="nc" id="L141">                fileBytes = byteArrayOutputStream.toByteArray();</span>
    +            }
    +
    +        } finally {
    +<span class="pc bpc" id="L145" title="1 of 2 branches missed.">            if (tempInputFile != null) Files.deleteIfExists(tempInputFile);</span>
    +<span class="pc bpc" id="L146" title="1 of 2 branches missed.">            if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());</span>
    +        }
    +<span class="fc" id="L148">        return WebResponseUtils.bytesToWebResponse(</span>
    +                fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
    +    }
    +
    +    public ResponseEntity&lt;byte[]&gt; processPdfToHtml(MultipartFile inputFile)
    +            throws IOException, InterruptedException {
    +<span class="nc bnc" id="L154" title="All 2 branches missed.">        if (!&quot;application/pdf&quot;.equals(inputFile.getContentType())) {</span>
    +<span class="nc" id="L155">            return new ResponseEntity&lt;&gt;(HttpStatus.BAD_REQUEST);</span>
    +        }
    +
    +        // Get the original PDF file name without the extension
    +<span class="nc" id="L159">        String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());</span>
    +<span class="nc" id="L160">        String pdfBaseName = originalPdfFileName;</span>
    +<span class="nc bnc" id="L161" title="All 2 branches missed.">        if (originalPdfFileName.contains(&quot;.&quot;)) {</span>
    +<span class="nc" id="L162">            pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));</span>
    +        }
    +
    +<span class="nc" id="L165">        Path tempInputFile = null;</span>
    +<span class="nc" id="L166">        Path tempOutputDir = null;</span>
    +        byte[] fileBytes;
    +<span class="nc" id="L168">        String fileName = &quot;temp.file&quot;;</span>
    +
    +        try {
    +            // Save the uploaded file to a temporary location
    +<span class="nc" id="L172">            tempInputFile = Files.createTempFile(&quot;input_&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L173">            inputFile.transferTo(tempInputFile);</span>
    +
    +            // Prepare the output directory
    +<span class="nc" id="L176">            tempOutputDir = Files.createTempDirectory(&quot;output_&quot;);</span>
    +
    +            // Run the pdftohtml command with complex output
    +<span class="nc" id="L179">            List&lt;String&gt; command =</span>
    +                    new ArrayList&lt;&gt;(
    +<span class="nc" id="L181">                            Arrays.asList(</span>
    +<span class="nc" id="L182">                                    &quot;pdftohtml&quot;, &quot;-c&quot;, tempInputFile.toString(), pdfBaseName));</span>
    +
    +<span class="nc" id="L184">            ProcessExecutorResult returnCode =</span>
    +<span class="nc" id="L185">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.PDFTOHTML)</span>
    +<span class="nc" id="L186">                            .runCommandWithOutputHandling(command, tempOutputDir.toFile());</span>
    +
    +            // Get output files
    +<span class="nc" id="L189">            File[] outputFiles = Objects.requireNonNull(tempOutputDir.toFile().listFiles());</span>
    +
    +            // Return output files in a ZIP archive
    +<span class="nc" id="L192">            fileName = pdfBaseName + &quot;ToHtml.zip&quot;;</span>
    +<span class="nc" id="L193">            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L194">            try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {</span>
    +<span class="nc bnc" id="L195" title="All 2 branches missed.">                for (File outputFile : outputFiles) {</span>
    +<span class="nc" id="L196">                    ZipEntry entry = new ZipEntry(outputFile.getName());</span>
    +<span class="nc" id="L197">                    zipOutputStream.putNextEntry(entry);</span>
    +<span class="nc" id="L198">                    try (FileInputStream fis = new FileInputStream(outputFile)) {</span>
    +<span class="nc" id="L199">                        IOUtils.copy(fis, zipOutputStream);</span>
    +<span class="nc" id="L200">                    } catch (IOException e) {</span>
    +<span class="nc" id="L201">                        log.error(&quot;Exception writing zip entry&quot;, e);</span>
    +<span class="nc" id="L202">                    }</span>
    +<span class="nc" id="L203">                    zipOutputStream.closeEntry();</span>
    +                }
    +<span class="nc" id="L205">            } catch (IOException e) {</span>
    +<span class="nc" id="L206">                log.error(&quot;Exception writing zip&quot;, e);</span>
    +<span class="nc" id="L207">            }</span>
    +<span class="nc" id="L208">            fileBytes = byteArrayOutputStream.toByteArray();</span>
    +
    +        } finally {
    +            // Clean up the temporary files
    +<span class="nc bnc" id="L212" title="All 2 branches missed.">            if (tempInputFile != null) Files.deleteIfExists(tempInputFile);</span>
    +<span class="nc bnc" id="L213" title="All 2 branches missed.">            if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());</span>
    +        }
    +
    +<span class="nc" id="L216">        return WebResponseUtils.bytesToWebResponse(</span>
    +                fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
    +    }
    +
    +    public ResponseEntity&lt;byte[]&gt; processPdfToOfficeFormat(
    +            MultipartFile inputFile, String outputFormat, String libreOfficeFilter)
    +            throws IOException, InterruptedException {
    +
    +<span class="nc bnc" id="L224" title="All 2 branches missed.">        if (!&quot;application/pdf&quot;.equals(inputFile.getContentType())) {</span>
    +<span class="nc" id="L225">            return new ResponseEntity&lt;&gt;(HttpStatus.BAD_REQUEST);</span>
    +        }
    +
    +        // Get the original PDF file name without the extension
    +<span class="nc" id="L229">        String originalPdfFileName = Filenames.toSimpleFileName(inputFile.getOriginalFilename());</span>
    +
    +<span class="nc bnc" id="L231" title="All 4 branches missed.">        if (originalPdfFileName == null || &quot;&quot;.equals(originalPdfFileName.trim())) {</span>
    +<span class="nc" id="L232">            originalPdfFileName = &quot;output.pdf&quot;;</span>
    +        }
    +        // Assume file is pdf if no extension
    +<span class="nc" id="L235">        String pdfBaseName = originalPdfFileName;</span>
    +<span class="nc bnc" id="L236" title="All 2 branches missed.">        if (originalPdfFileName.contains(&quot;.&quot;)) {</span>
    +<span class="nc" id="L237">            pdfBaseName = originalPdfFileName.substring(0, originalPdfFileName.lastIndexOf('.'));</span>
    +        }
    +        // Validate output format
    +<span class="nc" id="L240">        List&lt;String&gt; allowedFormats =</span>
    +<span class="nc" id="L241">                Arrays.asList(&quot;doc&quot;, &quot;docx&quot;, &quot;odt&quot;, &quot;ppt&quot;, &quot;pptx&quot;, &quot;odp&quot;, &quot;rtf&quot;, &quot;xml&quot;, &quot;txt:Text&quot;);</span>
    +<span class="nc bnc" id="L242" title="All 2 branches missed.">        if (!allowedFormats.contains(outputFormat)) {</span>
    +<span class="nc" id="L243">            return new ResponseEntity&lt;&gt;(HttpStatus.BAD_REQUEST);</span>
    +        }
    +
    +<span class="nc" id="L246">        Path tempInputFile = null;</span>
    +<span class="nc" id="L247">        Path tempOutputDir = null;</span>
    +        byte[] fileBytes;
    +<span class="nc" id="L249">        String fileName = &quot;temp.file&quot;;</span>
    +
    +        try {
    +            // Save the uploaded file to a temporary location
    +<span class="nc" id="L253">            tempInputFile = Files.createTempFile(&quot;input_&quot;, &quot;.pdf&quot;);</span>
    +<span class="nc" id="L254">            inputFile.transferTo(tempInputFile);</span>
    +
    +            // Prepare the output directory
    +<span class="nc" id="L257">            tempOutputDir = Files.createTempDirectory(&quot;output_&quot;);</span>
    +
    +            // Run the LibreOffice command
    +<span class="nc" id="L260">            List&lt;String&gt; command =</span>
    +                    new ArrayList&lt;&gt;(
    +<span class="nc" id="L262">                            Arrays.asList(</span>
    +                                    &quot;soffice&quot;,
    +                                    &quot;--headless&quot;,
    +                                    &quot;--nologo&quot;,
    +                                    &quot;--infilter=&quot; + libreOfficeFilter,
    +                                    &quot;--convert-to&quot;,
    +                                    outputFormat,
    +                                    &quot;--outdir&quot;,
    +<span class="nc" id="L270">                                    tempOutputDir.toString(),</span>
    +<span class="nc" id="L271">                                    tempInputFile.toString()));</span>
    +<span class="nc" id="L272">            ProcessExecutorResult returnCode =</span>
    +<span class="nc" id="L273">                    ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE)</span>
    +<span class="nc" id="L274">                            .runCommandWithOutputHandling(command);</span>
    +
    +            // Get output files
    +<span class="nc" id="L277">            List&lt;File&gt; outputFiles = Arrays.asList(tempOutputDir.toFile().listFiles());</span>
    +
    +<span class="nc bnc" id="L279" title="All 2 branches missed.">            if (outputFiles.size() == 1) {</span>
    +                // Return single output file
    +<span class="nc" id="L281">                File outputFile = outputFiles.get(0);</span>
    +<span class="nc bnc" id="L282" title="All 2 branches missed.">                if (&quot;txt:Text&quot;.equals(outputFormat)) {</span>
    +<span class="nc" id="L283">                    outputFormat = &quot;txt&quot;;</span>
    +                }
    +<span class="nc" id="L285">                fileName = pdfBaseName + &quot;.&quot; + outputFormat;</span>
    +<span class="nc" id="L286">                fileBytes = FileUtils.readFileToByteArray(outputFile);</span>
    +<span class="nc" id="L287">            } else {</span>
    +                // Return output files in a ZIP archive
    +<span class="nc" id="L289">                fileName = pdfBaseName + &quot;To&quot; + outputFormat + &quot;.zip&quot;;</span>
    +<span class="nc" id="L290">                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L291">                try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {</span>
    +<span class="nc bnc" id="L292" title="All 2 branches missed.">                    for (File outputFile : outputFiles) {</span>
    +<span class="nc" id="L293">                        ZipEntry entry = new ZipEntry(outputFile.getName());</span>
    +<span class="nc" id="L294">                        zipOutputStream.putNextEntry(entry);</span>
    +<span class="nc" id="L295">                        try (FileInputStream fis = new FileInputStream(outputFile)) {</span>
    +<span class="nc" id="L296">                            IOUtils.copy(fis, zipOutputStream);</span>
    +<span class="nc" id="L297">                        } catch (IOException e) {</span>
    +<span class="nc" id="L298">                            log.error(&quot;Exception writing zip entry&quot;, e);</span>
    +<span class="nc" id="L299">                        }</span>
    +
    +<span class="nc" id="L301">                        zipOutputStream.closeEntry();</span>
    +<span class="nc" id="L302">                    }</span>
    +<span class="nc" id="L303">                } catch (IOException e) {</span>
    +<span class="nc" id="L304">                    log.error(&quot;Exception writing zip&quot;, e);</span>
    +<span class="nc" id="L305">                }</span>
    +
    +<span class="nc" id="L307">                fileBytes = byteArrayOutputStream.toByteArray();</span>
    +            }
    +
    +        } finally {
    +            // Clean up the temporary files
    +<span class="nc" id="L312">            Files.deleteIfExists(tempInputFile);</span>
    +<span class="nc bnc" id="L313" title="All 2 branches missed.">            if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile());</span>
    +        }
    +<span class="nc" id="L315">        return WebResponseUtils.bytesToWebResponse(</span>
    +                fileBytes, fileName, MediaType.APPLICATION_OCTET_STREAM);
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/PdfUtils$PdfImageDimensionValue.html b/testResult/html/stirling.software.SPDF.utils/PdfUtils$PdfImageDimensionValue.html
    new file mode 100644
    index 000000000..03756c075
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/PdfUtils$PdfImageDimensionValue.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfUtils.PdfImageDimensionValue</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">PdfUtils.PdfImageDimensionValue</span></div><h1>PdfUtils.PdfImageDimensionValue</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">9 of 9</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="PdfUtils.java.html#L526" class="el_method">PdfUtils.PdfImageDimensionValue(int, int)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="9" alt="9"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/PdfUtils$PdfRenderSettingsKey.html b/testResult/html/stirling.software.SPDF.utils/PdfUtils$PdfRenderSettingsKey.html
    new file mode 100644
    index 000000000..c4a306113
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/PdfUtils$PdfRenderSettingsKey.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfUtils.PdfRenderSettingsKey</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">PdfUtils.PdfRenderSettingsKey</span></div><h1>PdfUtils.PdfRenderSettingsKey</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">12 of 12</td><td class="ctr2">0%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td><td class="ctr1">1</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="PdfUtils.java.html#L523" class="el_method">PdfUtils.PdfRenderSettingsKey(float, float, int)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">1</td><td class="ctr2" id="i0">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/PdfUtils.html b/testResult/html/stirling.software.SPDF.utils/PdfUtils.html
    new file mode 100644
    index 000000000..eafc12d30
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/PdfUtils.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfUtils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">PdfUtils</span></div><h1>PdfUtils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">1,115 of 1,115</td><td class="ctr2">0%</td><td class="bar">116 of 116</td><td class="ctr2">0%</td><td class="ctr1">80</td><td class="ctr2">80</td><td class="ctr1">258</td><td class="ctr2">258</td><td class="ctr1">17</td><td class="ctr2">17</td></tr></tfoot><tbody><tr><td id="a2"><a href="PdfUtils.java.html#L138" class="el_method">convertFromPdf(CustomPDFDocumentFactory, byte[], String, ImageType, boolean, int, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="298" alt="298"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="24" alt="24"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">13</td><td class="ctr2" id="g0">13</td><td class="ctr1" id="h0">72</td><td class="ctr2" id="i0">72</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a0"><a href="PdfUtils.java.html#L360" class="el_method">addImageToDocument(PDDocument, PDImageXObject, String, boolean)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="63" height="10" title="157" alt="157"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="16" alt="16"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">9</td><td class="ctr2" id="g2">9</td><td class="ctr1" id="h1">30</td><td class="ctr2" id="i1">30</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a9"><a href="PdfUtils.java.html#L320" class="el_method">imageToPdf(MultipartFile[], String, boolean, String, CustomPDFDocumentFactory)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="47" height="10" title="118" alt="118"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="70" height="10" title="14" alt="14"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f3">8</td><td class="ctr2" id="g3">8</td><td class="ctr1" id="h2">29</td><td class="ctr2" id="i2">29</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a1"><a href="PdfUtils.java.html#L448" class="el_method">containsTextInFile(PDDocument, String, String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="101" alt="101"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="10" alt="10"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f5">6</td><td class="ctr2" id="g5">6</td><td class="ctr1" id="h3">22</td><td class="ctr2" id="i3">22</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a3"><a href="PdfUtils.java.html#L275" class="el_method">convertPdfToPdfImage(PDDocument)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="74" alt="74"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f12">2</td><td class="ctr2" id="g12">2</td><td class="ctr1" id="h5">15</td><td class="ctr2" id="i5">15</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a10"><a href="PdfUtils.java.html#L416" class="el_method">overlayImage(CustomPDFDocumentFactory, byte[], byte[], float, float, boolean)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="27" height="10" title="68" alt="68"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="6" alt="6"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f6">4</td><td class="ctr2" id="g6">4</td><td class="ctr1" id="h4">18</td><td class="ctr2" id="i4">18</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a4"><a href="PdfUtils.java.html#L69" class="el_method">getAllImages(PDResources)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="42" alt="42"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="6" alt="6"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f7">4</td><td class="ctr2" id="g7">4</td><td class="ctr1" id="h7">9</td><td class="ctr2" id="i7">9</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a14"><a href="PdfUtils.java.html#L299" class="el_method">prepareImageForPdfToImage(int, int, String)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="41" alt="41"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f8">3</td><td class="ctr2" id="g8">3</td><td class="ctr1" id="h8">9</td><td class="ctr2" id="i8">9</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a12"><a href="PdfUtils.java.html#L504" class="el_method">pageSize(PDDocument, String)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="41" alt="41"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f9">3</td><td class="ctr2" id="g9">3</td><td class="ctr1" id="h9">9</td><td class="ctr2" id="i9">9</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a11"><a href="PdfUtils.java.html#L487" class="el_method">pageCount(PDDocument, int, String)</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="40" alt="40"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="10" alt="10"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f4">7</td><td class="ctr2" id="g4">7</td><td class="ctr1" id="h12">7</td><td class="ctr2" id="i12">7</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a16"><a href="PdfUtils.java.html#L44" class="el_method">textToPageSize(String)</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="33" alt="33"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="10" alt="10"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f1">10</td><td class="ctr2" id="g1">10</td><td class="ctr1" id="h6">11</td><td class="ctr2" id="i6">11</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a7"><a href="PdfUtils.java.html#L102" class="el_method">hasText(PDDocument, String, String)</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="33" alt="33"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f10">3</td><td class="ctr2" id="g10">3</td><td class="ctr1" id="h10">9</td><td class="ctr2" id="i10">9</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr><tr><td id="a5"><a href="PdfUtils.java.html#L86" class="el_method">hasImages(PDDocument, String)</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="12" height="10" title="32" alt="32"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="4" alt="4"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f11">3</td><td class="ctr2" id="g11">3</td><td class="ctr1" id="h11">9</td><td class="ctr2" id="i11">9</td><td class="ctr1" id="j12">1</td><td class="ctr2" id="k12">1</td></tr><tr><td id="a8"><a href="PdfUtils.java.html#L121" class="el_method">hasTextOnPage(PDPage, String)</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="21" alt="21"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f14">1</td><td class="ctr2" id="g14">1</td><td class="ctr1" id="h13">6</td><td class="ctr2" id="i13">6</td><td class="ctr1" id="j13">1</td><td class="ctr2" id="k13">1</td></tr><tr><td id="a6"><a href="PdfUtils.java.html#L117" class="el_method">hasImagesOnPage(PDPage)</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="9" alt="9"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d13"><img src="../jacoco-resources/redbar.gif" width="10" height="10" title="2" alt="2"/></td><td class="ctr2" id="e13">0%</td><td class="ctr1" id="f13">2</td><td class="ctr2" id="g13">2</td><td class="ctr1" id="h14">1</td><td class="ctr2" id="i14">1</td><td class="ctr1" id="j14">1</td><td class="ctr2" id="k14">1</td></tr><tr><td id="a15"><a href="PdfUtils.java.html#L40" class="el_method">static {...}</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="4" alt="4"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">1</td><td class="ctr2" id="g15">1</td><td class="ctr1" id="h15">1</td><td class="ctr2" id="i15">1</td><td class="ctr1" id="j15">1</td><td class="ctr2" id="k15">1</td></tr><tr><td id="a13"><a href="PdfUtils.java.html#L41" class="el_method">PdfUtils()</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="3" alt="3"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f16">1</td><td class="ctr2" id="g16">1</td><td class="ctr1" id="h16">1</td><td class="ctr2" id="i16">1</td><td class="ctr1" id="j16">1</td><td class="ctr2" id="k16">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/PdfUtils.java.html b/testResult/html/stirling.software.SPDF.utils/PdfUtils.java.html
    new file mode 100644
    index 000000000..88708b7e3
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/PdfUtils.java.html
    @@ -0,0 +1,528 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PdfUtils.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">PdfUtils.java</span></div><h1>PdfUtils.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.awt.*;
    +import java.awt.image.BufferedImage;
    +import java.awt.image.RenderedImage;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.zip.ZipEntry;
    +import java.util.zip.ZipOutputStream;
    +
    +import javax.imageio.*;
    +import javax.imageio.stream.ImageOutputStream;
    +
    +import org.apache.pdfbox.cos.COSName;
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.apache.pdfbox.pdmodel.PDPage;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream;
    +import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
    +import org.apache.pdfbox.pdmodel.PDResources;
    +import org.apache.pdfbox.pdmodel.common.PDRectangle;
    +import org.apache.pdfbox.pdmodel.graphics.PDXObject;
    +import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
    +import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
    +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
    +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
    +import org.apache.pdfbox.rendering.ImageType;
    +import org.apache.pdfbox.rendering.PDFRenderer;
    +import org.apache.pdfbox.text.PDFTextStripper;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.service.CustomPDFDocumentFactory;
    +
    +<span class="nc" id="L40">@Slf4j</span>
    +<span class="nc" id="L41">public class PdfUtils {</span>
    +
    +    public static PDRectangle textToPageSize(String size) {
    +<span class="nc bnc" id="L44" title="All 10 branches missed.">        switch (size.toUpperCase()) {</span>
    +            case &quot;A0&quot;:
    +<span class="nc" id="L46">                return PDRectangle.A0;</span>
    +            case &quot;A1&quot;:
    +<span class="nc" id="L48">                return PDRectangle.A1;</span>
    +            case &quot;A2&quot;:
    +<span class="nc" id="L50">                return PDRectangle.A2;</span>
    +            case &quot;A3&quot;:
    +<span class="nc" id="L52">                return PDRectangle.A3;</span>
    +            case &quot;A4&quot;:
    +<span class="nc" id="L54">                return PDRectangle.A4;</span>
    +            case &quot;A5&quot;:
    +<span class="nc" id="L56">                return PDRectangle.A5;</span>
    +            case &quot;A6&quot;:
    +<span class="nc" id="L58">                return PDRectangle.A6;</span>
    +            case &quot;LETTER&quot;:
    +<span class="nc" id="L60">                return PDRectangle.LETTER;</span>
    +            case &quot;LEGAL&quot;:
    +<span class="nc" id="L62">                return PDRectangle.LEGAL;</span>
    +            default:
    +<span class="nc" id="L64">                throw new IllegalArgumentException(&quot;Invalid standard page size: &quot; + size);</span>
    +        }
    +    }
    +
    +    public static List&lt;RenderedImage&gt; getAllImages(PDResources resources) throws IOException {
    +<span class="nc" id="L69">        List&lt;RenderedImage&gt; images = new ArrayList&lt;&gt;();</span>
    +
    +<span class="nc bnc" id="L71" title="All 2 branches missed.">        for (COSName name : resources.getXObjectNames()) {</span>
    +<span class="nc" id="L72">            PDXObject object = resources.getXObject(name);</span>
    +
    +<span class="nc bnc" id="L74" title="All 2 branches missed.">            if (object instanceof PDImageXObject) {</span>
    +<span class="nc" id="L75">                images.add(((PDImageXObject) object).getImage());</span>
    +
    +<span class="nc bnc" id="L77" title="All 2 branches missed.">            } else if (object instanceof PDFormXObject) {</span>
    +<span class="nc" id="L78">                images.addAll(getAllImages(((PDFormXObject) object).getResources()));</span>
    +            }
    +<span class="nc" id="L80">        }</span>
    +
    +<span class="nc" id="L82">        return images;</span>
    +    }
    +
    +    public static boolean hasImages(PDDocument document, String pagesToCheck) throws IOException {
    +<span class="nc" id="L86">        String[] pageOrderArr = pagesToCheck.split(&quot;,&quot;);</span>
    +<span class="nc" id="L87">        List&lt;Integer&gt; pageList =</span>
    +<span class="nc" id="L88">                GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());</span>
    +
    +<span class="nc bnc" id="L90" title="All 2 branches missed.">        for (int pageNumber : pageList) {</span>
    +<span class="nc" id="L91">            PDPage page = document.getPage(pageNumber);</span>
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">            if (hasImagesOnPage(page)) {</span>
    +<span class="nc" id="L93">                return true;</span>
    +            }
    +<span class="nc" id="L95">        }</span>
    +
    +<span class="nc" id="L97">        return false;</span>
    +    }
    +
    +    public static boolean hasText(PDDocument document, String pageNumbersToCheck, String phrase)
    +            throws IOException {
    +<span class="nc" id="L102">        String[] pageOrderArr = pageNumbersToCheck.split(&quot;,&quot;);</span>
    +<span class="nc" id="L103">        List&lt;Integer&gt; pageList =</span>
    +<span class="nc" id="L104">                GeneralUtils.parsePageList(pageOrderArr, document.getNumberOfPages());</span>
    +
    +<span class="nc bnc" id="L106" title="All 2 branches missed.">        for (int pageNumber : pageList) {</span>
    +<span class="nc" id="L107">            PDPage page = document.getPage(pageNumber);</span>
    +<span class="nc bnc" id="L108" title="All 2 branches missed.">            if (hasTextOnPage(page, phrase)) {</span>
    +<span class="nc" id="L109">                return true;</span>
    +            }
    +<span class="nc" id="L111">        }</span>
    +
    +<span class="nc" id="L113">        return false;</span>
    +    }
    +
    +    public static boolean hasImagesOnPage(PDPage page) throws IOException {
    +<span class="nc bnc" id="L117" title="All 2 branches missed.">        return getAllImages(page.getResources()).size() &gt; 0;</span>
    +    }
    +
    +    public static boolean hasTextOnPage(PDPage page, String phrase) throws IOException {
    +<span class="nc" id="L121">        PDFTextStripper textStripper = new PDFTextStripper();</span>
    +<span class="nc" id="L122">        PDDocument tempDoc = new PDDocument();</span>
    +<span class="nc" id="L123">        tempDoc.addPage(page);</span>
    +<span class="nc" id="L124">        String pageText = textStripper.getText(tempDoc);</span>
    +<span class="nc" id="L125">        tempDoc.close();</span>
    +<span class="nc" id="L126">        return pageText.contains(phrase);</span>
    +    }
    +
    +    public static byte[] convertFromPdf(
    +            CustomPDFDocumentFactory pdfDocumentFactory,
    +            byte[] inputStream,
    +            String imageType,
    +            ImageType colorType,
    +            boolean singleImage,
    +            int DPI,
    +            String filename)
    +            throws IOException, Exception {
    +<span class="nc" id="L138">        try (PDDocument document = pdfDocumentFactory.load(inputStream)) {</span>
    +<span class="nc" id="L139">            PDFRenderer pdfRenderer = new PDFRenderer(document);</span>
    +<span class="nc" id="L140">            pdfRenderer.setSubsamplingAllowed(true);</span>
    +<span class="nc" id="L141">            int pageCount = document.getNumberOfPages();</span>
    +
    +            // Create a ByteArrayOutputStream to save the image(s) to
    +<span class="nc" id="L144">            ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +
    +<span class="nc bnc" id="L146" title="All 2 branches missed.">            if (singleImage) {</span>
    +<span class="nc bnc" id="L147" title="All 2 branches missed.">                if (&quot;tiff&quot;.equals(imageType.toLowerCase())</span>
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">                        || &quot;tif&quot;.equals(imageType.toLowerCase())) {</span>
    +                    // Write the images to the output stream as a TIFF with multiple frames
    +<span class="nc" id="L150">                    ImageWriter writer = ImageIO.getImageWritersByFormatName(&quot;tiff&quot;).next();</span>
    +<span class="nc" id="L151">                    ImageWriteParam param = writer.getDefaultWriteParam();</span>
    +<span class="nc" id="L152">                    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);</span>
    +<span class="nc" id="L153">                    param.setCompressionType(&quot;ZLib&quot;);</span>
    +<span class="nc" id="L154">                    param.setCompressionQuality(1.0f);</span>
    +
    +<span class="nc" id="L156">                    try (ImageOutputStream ios = ImageIO.createImageOutputStream(baos)) {</span>
    +<span class="nc" id="L157">                        writer.setOutput(ios);</span>
    +<span class="nc" id="L158">                        writer.prepareWriteSequence(null);</span>
    +
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">                        for (int i = 0; i &lt; pageCount; ++i) {</span>
    +<span class="nc" id="L161">                            BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);</span>
    +<span class="nc" id="L162">                            writer.writeToSequence(new IIOImage(image, null, null), param);</span>
    +                        }
    +
    +<span class="nc" id="L165">                        writer.endWriteSequence();</span>
    +                    }
    +
    +<span class="nc" id="L168">                    writer.dispose();</span>
    +<span class="nc" id="L169">                } else {</span>
    +                    // Combine all images into a single big image
    +
    +                    // Calculate the combined image dimensions
    +<span class="nc" id="L173">                    int maxWidth = 0;</span>
    +<span class="nc" id="L174">                    int totalHeight = 0;</span>
    +
    +<span class="nc" id="L176">                    BufferedImage pdfSizeImage = null;</span>
    +<span class="nc" id="L177">                    int pdfSizeImageIndex = -1;</span>
    +
    +                    // Using a map to store the rendered dimensions of each page size
    +                    // to avoid rendering the same page sizes multiple times
    +<span class="nc" id="L181">                    HashMap&lt;PdfRenderSettingsKey, PdfImageDimensionValue&gt; pageSizes =</span>
    +                            new HashMap&lt;&gt;();
    +<span class="nc bnc" id="L183" title="All 2 branches missed.">                    for (int i = 0; i &lt; pageCount; ++i) {</span>
    +<span class="nc" id="L184">                        PDPage page = document.getPage(i);</span>
    +<span class="nc" id="L185">                        PDRectangle mediaBox = page.getMediaBox();</span>
    +<span class="nc" id="L186">                        int rotation = page.getRotation();</span>
    +<span class="nc" id="L187">                        PdfRenderSettingsKey settings =</span>
    +                                new PdfRenderSettingsKey(
    +<span class="nc" id="L189">                                        mediaBox.getWidth(), mediaBox.getHeight(), rotation);</span>
    +<span class="nc" id="L190">                        PdfImageDimensionValue dimension = pageSizes.get(settings);</span>
    +<span class="nc bnc" id="L191" title="All 2 branches missed.">                        if (dimension == null) {</span>
    +                            // Render the image to get the dimensions
    +<span class="nc" id="L193">                            pdfSizeImage = pdfRenderer.renderImageWithDPI(i, DPI, colorType);</span>
    +<span class="nc" id="L194">                            pdfSizeImageIndex = i;</span>
    +<span class="nc" id="L195">                            dimension =</span>
    +                                    new PdfImageDimensionValue(
    +<span class="nc" id="L197">                                            pdfSizeImage.getWidth(), pdfSizeImage.getHeight());</span>
    +<span class="nc" id="L198">                            pageSizes.put(settings, dimension);</span>
    +<span class="nc bnc" id="L199" title="All 2 branches missed.">                            if (pdfSizeImage.getWidth() &gt; maxWidth) {</span>
    +<span class="nc" id="L200">                                maxWidth = pdfSizeImage.getWidth();</span>
    +                            }
    +                        }
    +<span class="nc" id="L203">                        totalHeight += dimension.height();</span>
    +                    }
    +
    +                    // Create a new BufferedImage to store the combined images
    +<span class="nc" id="L207">                    BufferedImage combined =</span>
    +<span class="nc" id="L208">                            prepareImageForPdfToImage(maxWidth, totalHeight, imageType);</span>
    +<span class="nc" id="L209">                    Graphics g = combined.getGraphics();</span>
    +
    +<span class="nc" id="L211">                    int currentHeight = 0;</span>
    +                    BufferedImage pageImage;
    +
    +                    // Check if the first image is the last rendered image
    +<span class="nc bnc" id="L215" title="All 2 branches missed.">                    boolean firstImageAlreadyRendered = pdfSizeImageIndex == 0;</span>
    +
    +<span class="nc bnc" id="L217" title="All 2 branches missed.">                    for (int i = 0; i &lt; pageCount; ++i) {</span>
    +<span class="nc bnc" id="L218" title="All 4 branches missed.">                        if (firstImageAlreadyRendered &amp;&amp; i == 0) {</span>
    +<span class="nc" id="L219">                            pageImage = pdfSizeImage;</span>
    +                        } else {
    +<span class="nc" id="L221">                            pageImage = pdfRenderer.renderImageWithDPI(i, DPI, colorType);</span>
    +                        }
    +
    +                        // Calculate the x-coordinate to center the image
    +<span class="nc" id="L225">                        int x = (maxWidth - pageImage.getWidth()) / 2;</span>
    +
    +<span class="nc" id="L227">                        g.drawImage(pageImage, x, currentHeight, null);</span>
    +<span class="nc" id="L228">                        currentHeight += pageImage.getHeight();</span>
    +                    }
    +
    +                    // Write the image to the output stream
    +<span class="nc" id="L232">                    ImageIO.write(combined, imageType, baos);</span>
    +                }
    +
    +                // Log that the image was successfully written to the byte array
    +<span class="nc" id="L236">                log.info(&quot;Image successfully written to byte array&quot;);</span>
    +            } else {
    +                // Zip the images and return as byte array
    +<span class="nc" id="L239">                try (ZipOutputStream zos = new ZipOutputStream(baos)) {</span>
    +<span class="nc bnc" id="L240" title="All 2 branches missed.">                    for (int i = 0; i &lt; pageCount; ++i) {</span>
    +<span class="nc" id="L241">                        BufferedImage image = pdfRenderer.renderImageWithDPI(i, DPI, colorType);</span>
    +<span class="nc" id="L242">                        try (ByteArrayOutputStream baosImage = new ByteArrayOutputStream()) {</span>
    +<span class="nc" id="L243">                            ImageIO.write(image, imageType, baosImage);</span>
    +
    +                            // Add the image to the zip file
    +<span class="nc" id="L246">                            zos.putNextEntry(</span>
    +                                    new ZipEntry(
    +<span class="nc" id="L248">                                            String.format(</span>
    +                                                    filename + &quot;_%d.%s&quot;,
    +<span class="nc" id="L250">                                                    i + 1,</span>
    +<span class="nc" id="L251">                                                    imageType.toLowerCase())));</span>
    +<span class="nc" id="L252">                            zos.write(baosImage.toByteArray());</span>
    +                        }
    +                    }
    +                    // Log that the images were successfully written to the byte array
    +<span class="nc" id="L256">                    log.info(&quot;Images successfully written to byte array as a zip&quot;);</span>
    +                }
    +            }
    +<span class="nc" id="L259">            return baos.toByteArray();</span>
    +<span class="nc" id="L260">        } catch (IOException e) {</span>
    +            // Log an error message if there is an issue converting the PDF to an image
    +<span class="nc" id="L262">            log.error(&quot;Error converting PDF to image&quot;, e);</span>
    +<span class="nc" id="L263">            throw e;</span>
    +        }
    +    }
    +
    +    /**
    +     * Converts a given Pdf file to PDF-Image.
    +     *
    +     * @param document to be converted. Note: the caller is responsible for closing the document
    +     * @return converted document to PDF-Image
    +     * @throws IOException if conversion fails
    +     */
    +    public static PDDocument convertPdfToPdfImage(PDDocument document) throws IOException {
    +<span class="nc" id="L275">        PDDocument imageDocument = new PDDocument();</span>
    +<span class="nc" id="L276">        PDFRenderer pdfRenderer = new PDFRenderer(document);</span>
    +<span class="nc" id="L277">        pdfRenderer.setSubsamplingAllowed(true);</span>
    +<span class="nc bnc" id="L278" title="All 2 branches missed.">        for (int page = 0; page &lt; document.getNumberOfPages(); ++page) {</span>
    +<span class="nc" id="L279">            BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);</span>
    +<span class="nc" id="L280">            PDPage originalPage = document.getPage(page);</span>
    +
    +<span class="nc" id="L282">            float width = originalPage.getMediaBox().getWidth();</span>
    +<span class="nc" id="L283">            float height = originalPage.getMediaBox().getHeight();</span>
    +
    +<span class="nc" id="L285">            PDPage newPage = new PDPage(new PDRectangle(width, height));</span>
    +<span class="nc" id="L286">            imageDocument.addPage(newPage);</span>
    +<span class="nc" id="L287">            PDImageXObject pdImage = LosslessFactory.createFromImage(imageDocument, bim);</span>
    +<span class="nc" id="L288">            PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(imageDocument, newPage, AppendMode.APPEND, true, true);
    +<span class="nc" id="L290">            contentStream.drawImage(pdImage, 0, 0, width, height);</span>
    +<span class="nc" id="L291">            contentStream.close();</span>
    +        }
    +<span class="nc" id="L293">        return imageDocument;</span>
    +    }
    +
    +    private static BufferedImage prepareImageForPdfToImage(
    +            int maxWidth, int height, String imageType) {
    +        BufferedImage combined;
    +<span class="nc bnc" id="L299" title="All 2 branches missed.">        if (&quot;png&quot;.equalsIgnoreCase(imageType)) {</span>
    +<span class="nc" id="L300">            combined = new BufferedImage(maxWidth, height, BufferedImage.TYPE_INT_ARGB);</span>
    +        } else {
    +<span class="nc" id="L302">            combined = new BufferedImage(maxWidth, height, BufferedImage.TYPE_INT_RGB);</span>
    +        }
    +<span class="nc bnc" id="L304" title="All 2 branches missed.">        if (!&quot;png&quot;.equalsIgnoreCase(imageType)) {</span>
    +<span class="nc" id="L305">            Graphics g = combined.getGraphics();</span>
    +<span class="nc" id="L306">            g.setColor(Color.WHITE);</span>
    +<span class="nc" id="L307">            g.fillRect(0, 0, combined.getWidth(), combined.getHeight());</span>
    +<span class="nc" id="L308">            g.dispose();</span>
    +        }
    +<span class="nc" id="L310">        return combined;</span>
    +    }
    +
    +    public static byte[] imageToPdf(
    +            MultipartFile[] files,
    +            String fitOption,
    +            boolean autoRotate,
    +            String colorType,
    +            CustomPDFDocumentFactory pdfDocumentFactory)
    +            throws IOException {
    +<span class="nc" id="L320">        try (PDDocument doc = pdfDocumentFactory.createNewDocument()) {</span>
    +<span class="nc bnc" id="L321" title="All 2 branches missed.">            for (MultipartFile file : files) {</span>
    +<span class="nc" id="L322">                String contentType = file.getContentType();</span>
    +<span class="nc" id="L323">                String originalFilename = Filenames.toSimpleFileName(file.getOriginalFilename());</span>
    +<span class="nc bnc" id="L324" title="All 2 branches missed.">                if (originalFilename != null</span>
    +<span class="nc bnc" id="L325" title="All 2 branches missed.">                        &amp;&amp; (originalFilename.toLowerCase().endsWith(&quot;.tiff&quot;)</span>
    +<span class="nc bnc" id="L326" title="All 2 branches missed.">                                || originalFilename.toLowerCase().endsWith(&quot;.tif&quot;))) {</span>
    +<span class="nc" id="L327">                    ImageReader reader = ImageIO.getImageReadersByFormatName(&quot;tiff&quot;).next();</span>
    +<span class="nc" id="L328">                    reader.setInput(ImageIO.createImageInputStream(file.getInputStream()));</span>
    +<span class="nc" id="L329">                    int numPages = reader.getNumImages(true);</span>
    +<span class="nc bnc" id="L330" title="All 2 branches missed.">                    for (int i = 0; i &lt; numPages; i++) {</span>
    +<span class="nc" id="L331">                        BufferedImage pageImage = reader.read(i);</span>
    +<span class="nc" id="L332">                        BufferedImage convertedImage =</span>
    +<span class="nc" id="L333">                                ImageProcessingUtils.convertColorType(pageImage, colorType);</span>
    +<span class="nc" id="L334">                        PDImageXObject pdImage =</span>
    +<span class="nc" id="L335">                                LosslessFactory.createFromImage(doc, convertedImage);</span>
    +<span class="nc" id="L336">                        addImageToDocument(doc, pdImage, fitOption, autoRotate);</span>
    +                    }
    +<span class="nc" id="L338">                } else {</span>
    +<span class="nc" id="L339">                    BufferedImage image = ImageProcessingUtils.loadImageWithExifOrientation(file);</span>
    +<span class="nc" id="L340">                    BufferedImage convertedImage =</span>
    +<span class="nc" id="L341">                            ImageProcessingUtils.convertColorType(image, colorType);</span>
    +                    // Use JPEGFactory if it's JPEG since JPEG is lossy
    +                    PDImageXObject pdImage =
    +<span class="nc bnc" id="L344" title="All 4 branches missed.">                            (contentType != null &amp;&amp; &quot;image/jpeg&quot;.equals(contentType))</span>
    +<span class="nc" id="L345">                                    ? JPEGFactory.createFromImage(doc, convertedImage)</span>
    +<span class="nc" id="L346">                                    : LosslessFactory.createFromImage(doc, convertedImage);</span>
    +<span class="nc" id="L347">                    addImageToDocument(doc, pdImage, fitOption, autoRotate);</span>
    +                }
    +            }
    +<span class="nc" id="L350">            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L351">            doc.save(byteArrayOutputStream);</span>
    +<span class="nc" id="L352">            log.info(&quot;PDF successfully saved to byte array&quot;);</span>
    +<span class="nc" id="L353">            return byteArrayOutputStream.toByteArray();</span>
    +        }
    +    }
    +
    +    public static void addImageToDocument(
    +            PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate)
    +            throws IOException {
    +<span class="nc bnc" id="L360" title="All 2 branches missed.">        boolean imageIsLandscape = image.getWidth() &gt; image.getHeight();</span>
    +<span class="nc" id="L361">        PDRectangle pageSize = PDRectangle.A4;</span>
    +
    +<span class="nc bnc" id="L363" title="All 4 branches missed.">        if (autoRotate &amp;&amp; imageIsLandscape) {</span>
    +<span class="nc" id="L364">            pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth());</span>
    +        }
    +
    +<span class="nc bnc" id="L367" title="All 2 branches missed.">        if (&quot;fitDocumentToImage&quot;.equals(fitOption)) {</span>
    +<span class="nc" id="L368">            pageSize = new PDRectangle(image.getWidth(), image.getHeight());</span>
    +        }
    +
    +<span class="nc" id="L371">        PDPage page = new PDPage(pageSize);</span>
    +<span class="nc" id="L372">        doc.addPage(page);</span>
    +
    +<span class="nc" id="L374">        float pageWidth = page.getMediaBox().getWidth();</span>
    +<span class="nc" id="L375">        float pageHeight = page.getMediaBox().getHeight();</span>
    +
    +<span class="nc" id="L377">        try (PDPageContentStream contentStream =</span>
    +                new PDPageContentStream(doc, page, AppendMode.APPEND, true, true)) {
    +<span class="nc bnc" id="L379" title="All 4 branches missed.">            if (&quot;fillPage&quot;.equals(fitOption) || &quot;fitDocumentToImage&quot;.equals(fitOption)) {</span>
    +<span class="nc" id="L380">                contentStream.drawImage(image, 0, 0, pageWidth, pageHeight);</span>
    +<span class="nc bnc" id="L381" title="All 2 branches missed.">            } else if (&quot;maintainAspectRatio&quot;.equals(fitOption)) {</span>
    +<span class="nc" id="L382">                float imageAspectRatio = (float) image.getWidth() / (float) image.getHeight();</span>
    +<span class="nc" id="L383">                float pageAspectRatio = pageWidth / pageHeight;</span>
    +
    +<span class="nc" id="L385">                float scaleFactor = 1.0f;</span>
    +<span class="nc bnc" id="L386" title="All 2 branches missed.">                if (imageAspectRatio &gt; pageAspectRatio) {</span>
    +<span class="nc" id="L387">                    scaleFactor = pageWidth / image.getWidth();</span>
    +                } else {
    +<span class="nc" id="L389">                    scaleFactor = pageHeight / image.getHeight();</span>
    +                }
    +
    +<span class="nc" id="L392">                float xPos = (pageWidth - (image.getWidth() * scaleFactor)) / 2;</span>
    +<span class="nc" id="L393">                float yPos = (pageHeight - (image.getHeight() * scaleFactor)) / 2;</span>
    +<span class="nc" id="L394">                contentStream.drawImage(</span>
    +                        image,
    +                        xPos,
    +                        yPos,
    +<span class="nc" id="L398">                        image.getWidth() * scaleFactor,</span>
    +<span class="nc" id="L399">                        image.getHeight() * scaleFactor);</span>
    +            }
    +<span class="nc" id="L401">        } catch (IOException e) {</span>
    +<span class="nc" id="L402">            log.error(&quot;Error adding image to PDF&quot;, e);</span>
    +<span class="nc" id="L403">            throw e;</span>
    +<span class="nc" id="L404">        }</span>
    +<span class="nc" id="L405">    }</span>
    +
    +    public static byte[] overlayImage(
    +            CustomPDFDocumentFactory pdfDocumentFactory,
    +            byte[] pdfBytes,
    +            byte[] imageBytes,
    +            float x,
    +            float y,
    +            boolean everyPage)
    +            throws IOException {
    +
    +<span class="nc" id="L416">        PDDocument document = pdfDocumentFactory.load(pdfBytes);</span>
    +
    +        // Get the first page of the PDF
    +<span class="nc" id="L419">        int pages = document.getNumberOfPages();</span>
    +<span class="nc bnc" id="L420" title="All 2 branches missed.">        for (int i = 0; i &lt; pages; i++) {</span>
    +<span class="nc" id="L421">            PDPage page = document.getPage(i);</span>
    +<span class="nc" id="L422">            try (PDPageContentStream contentStream =</span>
    +                    new PDPageContentStream(
    +                            document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
    +                // Create an image object from the image bytes
    +<span class="nc" id="L426">                PDImageXObject image = PDImageXObject.createFromByteArray(document, imageBytes, &quot;&quot;);</span>
    +                // Draw the image onto the page at the specified x and y coordinates
    +<span class="nc" id="L428">                contentStream.drawImage(image, x, y);</span>
    +<span class="nc" id="L429">                log.info(&quot;Image successfully overlayed onto PDF&quot;);</span>
    +<span class="nc bnc" id="L430" title="All 4 branches missed.">                if (!everyPage &amp;&amp; i == 0) {</span>
    +<span class="nc" id="L431">                    break;</span>
    +                }
    +<span class="nc" id="L433">            } catch (IOException e) {</span>
    +                // Log an error message if there is an issue overlaying the image onto the PDF
    +<span class="nc" id="L435">                log.error(&quot;Error overlaying image onto PDF&quot;, e);</span>
    +<span class="nc" id="L436">                throw e;</span>
    +<span class="nc" id="L437">            }</span>
    +        }
    +        // Create a ByteArrayOutputStream to save the PDF to
    +<span class="nc" id="L440">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L441">        document.save(baos);</span>
    +<span class="nc" id="L442">        log.info(&quot;PDF successfully saved to byte array&quot;);</span>
    +<span class="nc" id="L443">        return baos.toByteArray();</span>
    +    }
    +
    +    public boolean containsTextInFile(PDDocument pdfDocument, String text, String pagesToCheck)
    +            throws IOException {
    +<span class="nc" id="L448">        PDFTextStripper textStripper = new PDFTextStripper();</span>
    +<span class="nc" id="L449">        String pdfText = &quot;&quot;;</span>
    +
    +<span class="nc bnc" id="L451" title="All 4 branches missed.">        if (pagesToCheck == null || &quot;all&quot;.equals(pagesToCheck)) {</span>
    +<span class="nc" id="L452">            pdfText = textStripper.getText(pdfDocument);</span>
    +        } else {
    +            // remove whitespaces
    +<span class="nc" id="L455">            pagesToCheck = pagesToCheck.replaceAll(&quot;\\s+&quot;, &quot;&quot;);</span>
    +
    +<span class="nc" id="L457">            String[] splitPoints = pagesToCheck.split(&quot;,&quot;);</span>
    +<span class="nc bnc" id="L458" title="All 2 branches missed.">            for (String splitPoint : splitPoints) {</span>
    +<span class="nc bnc" id="L459" title="All 2 branches missed.">                if (splitPoint.contains(&quot;-&quot;)) {</span>
    +                    // Handle page ranges
    +<span class="nc" id="L461">                    String[] range = splitPoint.split(&quot;-&quot;);</span>
    +<span class="nc" id="L462">                    int startPage = Integer.parseInt(range[0]);</span>
    +<span class="nc" id="L463">                    int endPage = Integer.parseInt(range[1]);</span>
    +
    +<span class="nc bnc" id="L465" title="All 2 branches missed.">                    for (int i = startPage; i &lt;= endPage; i++) {</span>
    +<span class="nc" id="L466">                        textStripper.setStartPage(i);</span>
    +<span class="nc" id="L467">                        textStripper.setEndPage(i);</span>
    +<span class="nc" id="L468">                        pdfText += textStripper.getText(pdfDocument);</span>
    +                    }
    +<span class="nc" id="L470">                } else {</span>
    +                    // Handle individual page
    +<span class="nc" id="L472">                    int page = Integer.parseInt(splitPoint);</span>
    +<span class="nc" id="L473">                    textStripper.setStartPage(page);</span>
    +<span class="nc" id="L474">                    textStripper.setEndPage(page);</span>
    +<span class="nc" id="L475">                    pdfText += textStripper.getText(pdfDocument);</span>
    +                }
    +            }
    +        }
    +
    +<span class="nc" id="L480">        pdfDocument.close();</span>
    +
    +<span class="nc" id="L482">        return pdfText.contains(text);</span>
    +    }
    +
    +    public boolean pageCount(PDDocument pdfDocument, int pageCount, String comparator)
    +            throws IOException {
    +<span class="nc" id="L487">        int actualPageCount = pdfDocument.getNumberOfPages();</span>
    +<span class="nc" id="L488">        pdfDocument.close();</span>
    +
    +<span class="nc bnc" id="L490" title="All 4 branches missed.">        switch (comparator.toLowerCase()) {</span>
    +            case &quot;greater&quot;:
    +<span class="nc bnc" id="L492" title="All 2 branches missed.">                return actualPageCount &gt; pageCount;</span>
    +            case &quot;equal&quot;:
    +<span class="nc bnc" id="L494" title="All 2 branches missed.">                return actualPageCount == pageCount;</span>
    +            case &quot;less&quot;:
    +<span class="nc bnc" id="L496" title="All 2 branches missed.">                return actualPageCount &lt; pageCount;</span>
    +            default:
    +<span class="nc" id="L498">                throw new IllegalArgumentException(</span>
    +                        &quot;Invalid comparator. Only 'greater', 'equal', and 'less' are supported.&quot;);
    +        }
    +    }
    +
    +    public boolean pageSize(PDDocument pdfDocument, String expectedPageSize) throws IOException {
    +<span class="nc" id="L504">        PDPage firstPage = pdfDocument.getPage(0);</span>
    +<span class="nc" id="L505">        PDRectangle mediaBox = firstPage.getMediaBox();</span>
    +
    +<span class="nc" id="L507">        float actualPageWidth = mediaBox.getWidth();</span>
    +<span class="nc" id="L508">        float actualPageHeight = mediaBox.getHeight();</span>
    +
    +<span class="nc" id="L510">        pdfDocument.close();</span>
    +
    +        // Assumes the expectedPageSize is in the format &quot;widthxheight&quot;, e.g. &quot;595x842&quot;
    +        // for A4
    +<span class="nc" id="L514">        String[] dimensions = expectedPageSize.split(&quot;x&quot;);</span>
    +<span class="nc" id="L515">        float expectedPageWidth = Float.parseFloat(dimensions[0]);</span>
    +<span class="nc" id="L516">        float expectedPageHeight = Float.parseFloat(dimensions[1]);</span>
    +
    +        // Checks if the actual page size matches the expected page size
    +<span class="nc bnc" id="L519" title="All 4 branches missed.">        return actualPageWidth == expectedPageWidth &amp;&amp; actualPageHeight == expectedPageHeight;</span>
    +    }
    +
    +    /** Key for storing the dimensions of a rendered image in a map. */
    +<span class="nc" id="L523">    private record PdfRenderSettingsKey(float mediaBoxWidth, float mediaBoxHeight, int rotation) {}</span>
    +
    +    /** Value for storing the dimensions of a rendered image in a map. */
    +<span class="nc" id="L526">    private record PdfImageDimensionValue(int width, int height) {}</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/ProcessExecutor$ProcessExecutorResult.html b/testResult/html/stirling.software.SPDF.utils/ProcessExecutor$ProcessExecutorResult.html
    new file mode 100644
    index 000000000..a182514f0
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/ProcessExecutor$ProcessExecutorResult.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ProcessExecutor.ProcessExecutorResult</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">ProcessExecutor.ProcessExecutorResult</span></div><h1>ProcessExecutor.ProcessExecutorResult</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">14 of 26</td><td class="ctr2">46%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">4</td><td class="ctr2">5</td><td class="ctr1">6</td><td class="ctr2">10</td><td class="ctr1">4</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a4"><a href="ProcessExecutor.java.html#L298" class="el_method">setRc(int)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">2</td><td class="ctr2" id="i1">2</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="ProcessExecutor.java.html#L306" class="el_method">setMessages(String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="ProcessExecutor.java.html#L294" class="el_method">getRc()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="ProcessExecutor.java.html#L302" class="el_method">getMessages()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a2"><a href="ProcessExecutor.java.html#L288" class="el_method">ProcessExecutor.ProcessExecutorResult(ProcessExecutor, int, String)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="12" alt="12"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">0</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">0</td><td class="ctr2" id="i0">4</td><td class="ctr1" id="j4">0</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/ProcessExecutor$Processes.html b/testResult/html/stirling.software.SPDF.utils/ProcessExecutor$Processes.html
    new file mode 100644
    index 000000000..0fce70511
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/ProcessExecutor$Processes.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ProcessExecutor.Processes</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">ProcessExecutor.Processes</span></div><h1>ProcessExecutor.Processes</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">0 of 51</td><td class="ctr2">100%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">0</td><td class="ctr2">1</td><td class="ctr1">0</td><td class="ctr2">9</td><td class="ctr1">0</td><td class="ctr2">1</td></tr></tfoot><tbody><tr><td id="a0"><a href="ProcessExecutor.java.html#L273" class="el_method">static {...}</a></td><td class="bar" id="b0"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="51" alt="51"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">0</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">0</td><td class="ctr2" id="i0">9</td><td class="ctr1" id="j0">0</td><td class="ctr2" id="k0">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/ProcessExecutor.html b/testResult/html/stirling.software.SPDF.utils/ProcessExecutor.html
    new file mode 100644
    index 000000000..1cc3b0bdf
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/ProcessExecutor.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ProcessExecutor</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">ProcessExecutor</span></div><h1>ProcessExecutor</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">139 of 395</td><td class="ctr2">64%</td><td class="bar">32 of 54</td><td class="ctr2">40%</td><td class="ctr1">30</td><td class="ctr2">42</td><td class="ctr1">75</td><td class="ctr2">149</td><td class="ctr1">1</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a2"><a href="ProcessExecutor.java.html#L45" class="el_method">lambda$getInstance$0(boolean, ProcessExecutor.Processes)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="68" alt="68"/><img src="../jacoco-resources/greenbar.gif" width="16" height="10" title="25" alt="25"/></td><td class="ctr2" id="c7">26%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="56" height="10" title="14" alt="14"/><img src="../jacoco-resources/greenbar.gif" width="8" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">12%</td><td class="ctr1" id="f0">14</td><td class="ctr2" id="g1">15</td><td class="ctr1" id="h0">56</td><td class="ctr2" id="i0">69</td><td class="ctr1" id="j1">0</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a7"><a href="ProcessExecutor.java.html#L143" class="el_method">runCommandWithOutputHandling(List, File)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="46" alt="46"/><img src="../jacoco-resources/greenbar.gif" width="89" height="10" title="135" alt="135"/></td><td class="ctr2" id="c4">74%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="64" height="10" title="16" alt="16"/><img src="../jacoco-resources/greenbar.gif" width="56" height="10" title="14" alt="14"/></td><td class="ctr2" id="e2">46%</td><td class="ctr1" id="f1">13</td><td class="ctr2" id="g0">16</td><td class="ctr1" id="h1">10</td><td class="ctr2" id="i1">45</td><td class="ctr1" id="j2">0</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="ProcessExecutor.java.html#L187" class="el_method">lambda$runCommandWithOutputHandling$2(Process, List)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="10" alt="10"/><img src="../jacoco-resources/greenbar.gif" width="19" height="10" title="29" alt="29"/></td><td class="ctr2" id="c5">74%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="1" alt="1"/><img src="../jacoco-resources/greenbar.gif" width="12" height="10" title="3" alt="3"/></td><td class="ctr2" id="e0">75%</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">12</td><td class="ctr1" id="j3">0</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="ProcessExecutor.java.html#L164" class="el_method">lambda$runCommandWithOutputHandling$1(Process, List)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="10" alt="10"/><img src="../jacoco-resources/greenbar.gif" width="19" height="10" title="29" alt="29"/></td><td class="ctr2" id="c6">74%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="1" alt="1"/><img src="../jacoco-resources/greenbar.gif" width="12" height="10" title="3" alt="3"/></td><td class="ctr2" id="e1">75%</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">12</td><td class="ctr1" id="j4">0</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a6"><a href="ProcessExecutor.java.html#L138" class="el_method">runCommandWithOutputHandling(List)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="5" alt="5"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a5"><a href="ProcessExecutor.java.html#L31" class="el_method">ProcessExecutor(int, boolean, long)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/greenbar.gif" width="9" height="10" title="15" alt="15"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">0</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">0</td><td class="ctr2" id="i4">5</td><td class="ctr1" id="j5">0</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a8"><a href="ProcessExecutor.java.html#L22" class="el_method">static {...}</a></td><td class="bar" id="b6"><img src="../jacoco-resources/greenbar.gif" width="7" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">0</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">0</td><td class="ctr2" id="i5">3</td><td class="ctr1" id="j6">0</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a1"><a href="ProcessExecutor.java.html#L42" class="el_method">getInstance(ProcessExecutor.Processes, boolean)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/greenbar.gif" width="4" height="10" title="7" alt="7"/></td><td class="ctr2" id="c2">100%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">0</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">0</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">0</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a0"><a href="ProcessExecutor.java.html#L38" class="el_method">getInstance(ProcessExecutor.Processes)</a></td><td class="bar" id="b8"><img src="../jacoco-resources/greenbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">100%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">0</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">0</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">0</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/ProcessExecutor.java.html b/testResult/html/stirling.software.SPDF.utils/ProcessExecutor.java.html
    new file mode 100644
    index 000000000..6fb9c24d2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/ProcessExecutor.java.html
    @@ -0,0 +1,310 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>ProcessExecutor.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">ProcessExecutor.java</span></div><h1>ProcessExecutor.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.io.BufferedReader;
    +import java.io.File;
    +import java.io.IOException;
    +import java.io.InputStreamReader;
    +import java.io.InterruptedIOException;
    +import java.nio.charset.StandardCharsets;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.concurrent.Semaphore;
    +import java.util.concurrent.TimeUnit;
    +
    +import io.github.pixee.security.BoundedLineReader;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.model.ApplicationProperties;
    +
    +<span class="fc" id="L22">@Slf4j</span>
    +public class ProcessExecutor {
    +
    +<span class="fc" id="L25">    private static final Map&lt;Processes, ProcessExecutor&gt; instances = new ConcurrentHashMap&lt;&gt;();</span>
    +<span class="fc" id="L26">    private static ApplicationProperties applicationProperties = new ApplicationProperties();</span>
    +    private final Semaphore semaphore;
    +    private final boolean liveUpdates;
    +    private long timeoutDuration;
    +
    +<span class="fc" id="L31">    private ProcessExecutor(int semaphoreLimit, boolean liveUpdates, long timeout) {</span>
    +<span class="fc" id="L32">        this.semaphore = new Semaphore(semaphoreLimit);</span>
    +<span class="fc" id="L33">        this.liveUpdates = liveUpdates;</span>
    +<span class="fc" id="L34">        this.timeoutDuration = timeout;</span>
    +<span class="fc" id="L35">    }</span>
    +
    +    public static ProcessExecutor getInstance(Processes processType) {
    +<span class="fc" id="L38">        return getInstance(processType, true);</span>
    +    }
    +
    +    public static ProcessExecutor getInstance(Processes processType, boolean liveUpdates) {
    +<span class="fc" id="L42">        return instances.computeIfAbsent(</span>
    +                processType,
    +                key -&gt; {
    +<span class="fc" id="L45">                    int semaphoreLimit =</span>
    +<span class="pc bpc" id="L46" title="7 of 8 branches missed.">                            switch (key) {</span>
    +                                case LIBRE_OFFICE -&gt;
    +<span class="nc" id="L48">                                        applicationProperties</span>
    +<span class="nc" id="L49">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L50">                                                .getSessionLimit()</span>
    +<span class="nc" id="L51">                                                .getLibreOfficeSessionLimit();</span>
    +                                case PDFTOHTML -&gt;
    +<span class="fc" id="L53">                                        applicationProperties</span>
    +<span class="fc" id="L54">                                                .getProcessExecutor()</span>
    +<span class="fc" id="L55">                                                .getSessionLimit()</span>
    +<span class="fc" id="L56">                                                .getPdfToHtmlSessionLimit();</span>
    +                                case PYTHON_OPENCV -&gt;
    +<span class="nc" id="L58">                                        applicationProperties</span>
    +<span class="nc" id="L59">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L60">                                                .getSessionLimit()</span>
    +<span class="nc" id="L61">                                                .getPythonOpenCvSessionLimit();</span>
    +                                case WEASYPRINT -&gt;
    +<span class="nc" id="L63">                                        applicationProperties</span>
    +<span class="nc" id="L64">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L65">                                                .getSessionLimit()</span>
    +<span class="nc" id="L66">                                                .getWeasyPrintSessionLimit();</span>
    +                                case INSTALL_APP -&gt;
    +<span class="nc" id="L68">                                        applicationProperties</span>
    +<span class="nc" id="L69">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L70">                                                .getSessionLimit()</span>
    +<span class="nc" id="L71">                                                .getInstallAppSessionLimit();</span>
    +                                case TESSERACT -&gt;
    +<span class="nc" id="L73">                                        applicationProperties</span>
    +<span class="nc" id="L74">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L75">                                                .getSessionLimit()</span>
    +<span class="nc" id="L76">                                                .getTesseractSessionLimit();</span>
    +                                case QPDF -&gt;
    +<span class="nc" id="L78">                                        applicationProperties</span>
    +<span class="nc" id="L79">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L80">                                                .getSessionLimit()</span>
    +<span class="nc" id="L81">                                                .getQpdfSessionLimit();</span>
    +                                case CALIBRE -&gt;
    +<span class="nc" id="L83">                                        applicationProperties</span>
    +<span class="nc" id="L84">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L85">                                                .getSessionLimit()</span>
    +<span class="nc" id="L86">                                                .getCalibreSessionLimit();</span>
    +                            };
    +
    +<span class="fc" id="L89">                    long timeoutMinutes =</span>
    +<span class="pc bpc" id="L90" title="7 of 8 branches missed.">                            switch (key) {</span>
    +                                case LIBRE_OFFICE -&gt;
    +<span class="nc" id="L92">                                        applicationProperties</span>
    +<span class="nc" id="L93">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L94">                                                .getTimeoutMinutes()</span>
    +<span class="nc" id="L95">                                                .getLibreOfficeTimeoutMinutes();</span>
    +                                case PDFTOHTML -&gt;
    +<span class="fc" id="L97">                                        applicationProperties</span>
    +<span class="fc" id="L98">                                                .getProcessExecutor()</span>
    +<span class="fc" id="L99">                                                .getTimeoutMinutes()</span>
    +<span class="fc" id="L100">                                                .getPdfToHtmlTimeoutMinutes();</span>
    +                                case PYTHON_OPENCV -&gt;
    +<span class="nc" id="L102">                                        applicationProperties</span>
    +<span class="nc" id="L103">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L104">                                                .getTimeoutMinutes()</span>
    +<span class="nc" id="L105">                                                .getPythonOpenCvTimeoutMinutes();</span>
    +                                case WEASYPRINT -&gt;
    +<span class="nc" id="L107">                                        applicationProperties</span>
    +<span class="nc" id="L108">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L109">                                                .getTimeoutMinutes()</span>
    +<span class="nc" id="L110">                                                .getWeasyPrintTimeoutMinutes();</span>
    +                                case INSTALL_APP -&gt;
    +<span class="nc" id="L112">                                        applicationProperties</span>
    +<span class="nc" id="L113">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L114">                                                .getTimeoutMinutes()</span>
    +<span class="nc" id="L115">                                                .getInstallAppTimeoutMinutes();</span>
    +                                case TESSERACT -&gt;
    +<span class="nc" id="L117">                                        applicationProperties</span>
    +<span class="nc" id="L118">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L119">                                                .getTimeoutMinutes()</span>
    +<span class="nc" id="L120">                                                .getTesseractTimeoutMinutes();</span>
    +                                case QPDF -&gt;
    +<span class="nc" id="L122">                                        applicationProperties</span>
    +<span class="nc" id="L123">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L124">                                                .getTimeoutMinutes()</span>
    +<span class="nc" id="L125">                                                .getQpdfTimeoutMinutes();</span>
    +                                case CALIBRE -&gt;
    +<span class="nc" id="L127">                                        applicationProperties</span>
    +<span class="nc" id="L128">                                                .getProcessExecutor()</span>
    +<span class="nc" id="L129">                                                .getTimeoutMinutes()</span>
    +<span class="nc" id="L130">                                                .getCalibreTimeoutMinutes();</span>
    +                            };
    +<span class="fc" id="L132">                    return new ProcessExecutor(semaphoreLimit, liveUpdates, timeoutMinutes);</span>
    +                });
    +    }
    +
    +    public ProcessExecutorResult runCommandWithOutputHandling(List&lt;String&gt; command)
    +            throws IOException, InterruptedException {
    +<span class="nc" id="L138">        return runCommandWithOutputHandling(command, null);</span>
    +    }
    +
    +    public ProcessExecutorResult runCommandWithOutputHandling(
    +            List&lt;String&gt; command, File workingDirectory) throws IOException, InterruptedException {
    +<span class="fc" id="L143">        String messages = &quot;&quot;;</span>
    +<span class="fc" id="L144">        int exitCode = 1;</span>
    +<span class="fc" id="L145">        semaphore.acquire();</span>
    +        try {
    +
    +<span class="fc" id="L148">            log.info(&quot;Running command: &quot; + String.join(&quot; &quot;, command));</span>
    +<span class="fc" id="L149">            ProcessBuilder processBuilder = new ProcessBuilder(command);</span>
    +
    +            // Use the working directory if it's set
    +<span class="pc bpc" id="L152" title="1 of 2 branches missed.">            if (workingDirectory != null) {</span>
    +<span class="fc" id="L153">                processBuilder.directory(workingDirectory);</span>
    +            }
    +<span class="fc" id="L155">            Process process = processBuilder.start();</span>
    +
    +            // Read the error stream and standard output stream concurrently
    +<span class="fc" id="L158">            List&lt;String&gt; errorLines = new ArrayList&lt;&gt;();</span>
    +<span class="fc" id="L159">            List&lt;String&gt; outputLines = new ArrayList&lt;&gt;();</span>
    +
    +<span class="fc" id="L161">            Thread errorReaderThread =</span>
    +                    new Thread(
    +                            () -&gt; {
    +<span class="fc" id="L164">                                try (BufferedReader errorReader =</span>
    +                                        new BufferedReader(
    +                                                new InputStreamReader(
    +<span class="fc" id="L167">                                                        process.getErrorStream(),</span>
    +                                                        StandardCharsets.UTF_8))) {
    +                                    String line;
    +<span class="fc" id="L170">                                    while ((line =</span>
    +<span class="fc bfc" id="L171" title="All 2 branches covered.">                                                    BoundedLineReader.readLine(</span>
    +                                                            errorReader, 5_000_000))
    +                                            != null) {
    +<span class="fc" id="L174">                                        errorLines.add(line);</span>
    +<span class="pc bpc" id="L175" title="1 of 2 branches missed.">                                        if (liveUpdates) log.info(line);</span>
    +                                    }
    +<span class="nc" id="L177">                                } catch (InterruptedIOException e) {</span>
    +<span class="nc" id="L178">                                    log.warn(&quot;Error reader thread was interrupted due to timeout.&quot;);</span>
    +<span class="nc" id="L179">                                } catch (IOException e) {</span>
    +<span class="nc" id="L180">                                    log.error(&quot;exception&quot;, e);</span>
    +<span class="pc" id="L181">                                }</span>
    +<span class="fc" id="L182">                            });</span>
    +
    +<span class="fc" id="L184">            Thread outputReaderThread =</span>
    +                    new Thread(
    +                            () -&gt; {
    +<span class="fc" id="L187">                                try (BufferedReader outputReader =</span>
    +                                        new BufferedReader(
    +                                                new InputStreamReader(
    +<span class="fc" id="L190">                                                        process.getInputStream(),</span>
    +                                                        StandardCharsets.UTF_8))) {
    +                                    String line;
    +<span class="fc" id="L193">                                    while ((line =</span>
    +<span class="fc bfc" id="L194" title="All 2 branches covered.">                                                    BoundedLineReader.readLine(</span>
    +                                                            outputReader, 5_000_000))
    +                                            != null) {
    +<span class="fc" id="L197">                                        outputLines.add(line);</span>
    +<span class="pc bpc" id="L198" title="1 of 2 branches missed.">                                        if (liveUpdates) log.info(line);</span>
    +                                    }
    +<span class="nc" id="L200">                                } catch (InterruptedIOException e) {</span>
    +<span class="nc" id="L201">                                    log.warn(&quot;Error reader thread was interrupted due to timeout.&quot;);</span>
    +<span class="nc" id="L202">                                } catch (IOException e) {</span>
    +<span class="nc" id="L203">                                    log.error(&quot;exception&quot;, e);</span>
    +<span class="pc" id="L204">                                }</span>
    +<span class="fc" id="L205">                            });</span>
    +
    +<span class="fc" id="L207">            errorReaderThread.start();</span>
    +<span class="fc" id="L208">            outputReaderThread.start();</span>
    +
    +            // Wait for the conversion process to complete
    +<span class="fc" id="L211">            boolean finished = process.waitFor(timeoutDuration, TimeUnit.MINUTES);</span>
    +
    +<span class="pc bpc" id="L213" title="1 of 2 branches missed.">            if (!finished) {</span>
    +                // Terminate the process
    +<span class="nc" id="L215">                process.destroy();</span>
    +                // Interrupt the reader threads
    +<span class="nc" id="L217">                errorReaderThread.interrupt();</span>
    +<span class="nc" id="L218">                outputReaderThread.interrupt();</span>
    +<span class="nc" id="L219">                throw new IOException(&quot;Process timeout exceeded.&quot;);</span>
    +            }
    +<span class="fc" id="L221">            exitCode = process.exitValue();</span>
    +            // Wait for the reader threads to finish
    +<span class="fc" id="L223">            errorReaderThread.join();</span>
    +<span class="fc" id="L224">            outputReaderThread.join();</span>
    +
    +<span class="pc bpc" id="L226" title="1 of 2 branches missed.">            boolean isQpdf =</span>
    +<span class="pc bpc" id="L227" title="2 of 4 branches missed.">                    command != null &amp;&amp; !command.isEmpty() &amp;&amp; command.get(0).contains(&quot;qpdf&quot;);</span>
    +
    +<span class="fc bfc" id="L229" title="All 2 branches covered.">            if (!outputLines.isEmpty()) {</span>
    +<span class="fc" id="L230">                String outputMessage = String.join(&quot;\n&quot;, outputLines);</span>
    +<span class="fc" id="L231">                messages += outputMessage;</span>
    +<span class="pc bpc" id="L232" title="1 of 2 branches missed.">                if (!liveUpdates) {</span>
    +<span class="nc" id="L233">                    log.info(&quot;Command output:\n&quot; + outputMessage);</span>
    +                }
    +            }
    +
    +<span class="fc bfc" id="L237" title="All 2 branches covered.">            if (!errorLines.isEmpty()) {</span>
    +<span class="fc" id="L238">                String errorMessage = String.join(&quot;\n&quot;, errorLines);</span>
    +<span class="fc" id="L239">                messages += errorMessage;</span>
    +<span class="pc bpc" id="L240" title="1 of 2 branches missed.">                if (!liveUpdates) {</span>
    +<span class="nc" id="L241">                    log.warn(&quot;Command error output:\n&quot; + errorMessage);</span>
    +                }
    +<span class="pc bpc" id="L243" title="1 of 2 branches missed.">                if (exitCode != 0) {</span>
    +<span class="pc bpc" id="L244" title="3 of 4 branches missed.">                    if (isQpdf &amp;&amp; exitCode == 3) {</span>
    +<span class="nc" id="L245">                        log.warn(&quot;qpdf succeeded with warnings: {}&quot;, messages);</span>
    +                    } else {
    +<span class="fc" id="L247">                        throw new IOException(</span>
    +                                &quot;Command process failed with exit code &quot;
    +                                        + exitCode
    +                                        + &quot;. Error message: &quot;
    +                                        + errorMessage);
    +                    }
    +                }
    +            }
    +
    +<span class="pc bpc" id="L256" title="1 of 2 branches missed.">            if (exitCode != 0) {</span>
    +<span class="nc bnc" id="L257" title="All 4 branches missed.">                if (isQpdf &amp;&amp; exitCode == 3) {</span>
    +<span class="nc" id="L258">                    log.warn(&quot;qpdf succeeded with warnings: {}&quot;, messages);</span>
    +                } else {
    +<span class="nc" id="L260">                    throw new IOException(</span>
    +                            &quot;Command process failed with exit code &quot;
    +                                    + exitCode
    +                                    + &quot;\nLogs: &quot;
    +                                    + messages);
    +                }
    +            }
    +        } finally {
    +<span class="fc" id="L268">            semaphore.release();</span>
    +        }
    +<span class="fc" id="L270">        return new ProcessExecutorResult(exitCode, messages);</span>
    +    }
    +
    +<span class="fc" id="L273">    public enum Processes {</span>
    +<span class="fc" id="L274">        LIBRE_OFFICE,</span>
    +<span class="fc" id="L275">        PDFTOHTML,</span>
    +<span class="fc" id="L276">        PYTHON_OPENCV,</span>
    +<span class="fc" id="L277">        WEASYPRINT,</span>
    +<span class="fc" id="L278">        INSTALL_APP,</span>
    +<span class="fc" id="L279">        CALIBRE,</span>
    +<span class="fc" id="L280">        TESSERACT,</span>
    +<span class="fc" id="L281">        QPDF</span>
    +    }
    +
    +    public class ProcessExecutorResult {
    +        int rc;
    +        String messages;
    +
    +<span class="fc" id="L288">        public ProcessExecutorResult(int rc, String messages) {</span>
    +<span class="fc" id="L289">            this.rc = rc;</span>
    +<span class="fc" id="L290">            this.messages = messages;</span>
    +<span class="fc" id="L291">        }</span>
    +
    +        public int getRc() {
    +<span class="nc" id="L294">            return rc;</span>
    +        }
    +
    +        public void setRc(int rc) {
    +<span class="nc" id="L298">            this.rc = rc;</span>
    +<span class="nc" id="L299">        }</span>
    +
    +        public String getMessages() {
    +<span class="nc" id="L302">            return messages;</span>
    +        }
    +
    +        public void setMessages(String messages) {
    +<span class="nc" id="L306">            this.messages = messages;</span>
    +<span class="nc" id="L307">        }</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/PropertyConfigs.html b/testResult/html/stirling.software.SPDF.utils/PropertyConfigs.html
    new file mode 100644
    index 000000000..b1a2d5c41
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/PropertyConfigs.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PropertyConfigs</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">PropertyConfigs</span></div><h1>PropertyConfigs</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">85 of 85</td><td class="ctr2">0%</td><td class="bar">20 of 20</td><td class="ctr2">0%</td><td class="ctr1">15</td><td class="ctr2">15</td><td class="ctr1">19</td><td class="ctr2">19</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a0"><a href="PropertyConfigs.java.html#L8" class="el_method">getBooleanValue(List, boolean)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="27" alt="27"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">4</td><td class="ctr2" id="g0">4</td><td class="ctr1" id="h0">6</td><td class="ctr2" id="i0">6</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="PropertyConfigs.java.html#L18" class="el_method">getStringValue(List, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="111" height="10" title="25" alt="25"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="6" alt="6"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">4</td><td class="ctr2" id="g1">4</td><td class="ctr1" id="h1">6</td><td class="ctr2" id="i1">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="PropertyConfigs.java.html#L28" class="el_method">getBooleanValue(String, boolean)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="71" height="10" title="16" alt="16"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">3</td><td class="ctr2" id="g2">3</td><td class="ctr1" id="h2">3</td><td class="ctr2" id="i2">3</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="PropertyConfigs.java.html#L34" class="el_method">getStringValue(String, String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="62" height="10" title="14" alt="14"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="4" alt="4"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">3</td><td class="ctr2" id="g3">3</td><td class="ctr1" id="h3">3</td><td class="ctr2" id="i3">3</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="PropertyConfigs.java.html#L5" class="el_method">PropertyConfigs()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/PropertyConfigs.java.html b/testResult/html/stirling.software.SPDF.utils/PropertyConfigs.java.html
    new file mode 100644
    index 000000000..6cd0b3954
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/PropertyConfigs.java.html
    @@ -0,0 +1,39 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>PropertyConfigs.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">PropertyConfigs.java</span></div><h1>PropertyConfigs.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.util.List;
    +
    +<span class="nc" id="L5">public class PropertyConfigs {</span>
    +
    +    public static boolean getBooleanValue(List&lt;String&gt; keys, boolean defaultValue) {
    +<span class="nc bnc" id="L8" title="All 2 branches missed.">        for (String key : keys) {</span>
    +<span class="nc" id="L9">            String value = System.getProperty(key);</span>
    +<span class="nc bnc" id="L10" title="All 2 branches missed.">            if (value == null) value = System.getenv(key);</span>
    +
    +<span class="nc bnc" id="L12" title="All 2 branches missed.">            if (value != null) return Boolean.valueOf(value);</span>
    +<span class="nc" id="L13">        }</span>
    +<span class="nc" id="L14">        return defaultValue;</span>
    +    }
    +
    +    public static String getStringValue(List&lt;String&gt; keys, String defaultValue) {
    +<span class="nc bnc" id="L18" title="All 2 branches missed.">        for (String key : keys) {</span>
    +<span class="nc" id="L19">            String value = System.getProperty(key);</span>
    +<span class="nc bnc" id="L20" title="All 2 branches missed.">            if (value == null) value = System.getenv(key);</span>
    +
    +<span class="nc bnc" id="L22" title="All 2 branches missed.">            if (value != null) return value;</span>
    +<span class="nc" id="L23">        }</span>
    +<span class="nc" id="L24">        return defaultValue;</span>
    +    }
    +
    +    public static boolean getBooleanValue(String key, boolean defaultValue) {
    +<span class="nc" id="L28">        String value = System.getProperty(key);</span>
    +<span class="nc bnc" id="L29" title="All 2 branches missed.">        if (value == null) value = System.getenv(key);</span>
    +<span class="nc bnc" id="L30" title="All 2 branches missed.">        return (value != null) ? Boolean.valueOf(value) : defaultValue;</span>
    +    }
    +
    +    public static String getStringValue(String key, String defaultValue) {
    +<span class="nc" id="L34">        String value = System.getProperty(key);</span>
    +<span class="nc bnc" id="L35" title="All 2 branches missed.">        if (value == null) value = System.getenv(key);</span>
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">        return (value != null) ? value : defaultValue;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/RequestUriUtils.html b/testResult/html/stirling.software.SPDF.utils/RequestUriUtils.html
    new file mode 100644
    index 000000000..5f9118c6a
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/RequestUriUtils.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RequestUriUtils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">RequestUriUtils</span></div><h1>RequestUriUtils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">149 of 149</td><td class="ctr2">0%</td><td class="bar">60 of 60</td><td class="ctr2">0%</td><td class="ctr1">35</td><td class="ctr2">35</td><td class="ctr1">33</td><td class="ctr2">33</td><td class="ctr1">5</td><td class="ctr2">5</td></tr></tfoot><tbody><tr><td id="a1"><a href="RequestUriUtils.java.html#L12" class="el_method">isStaticResource(String, String)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="70" alt="70"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="105" height="10" title="28" alt="28"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">15</td><td class="ctr2" id="g1">15</td><td class="ctr1" id="h1">14</td><td class="ctr2" id="i1">14</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="RequestUriUtils.java.html#L33" class="el_method">isTrackableResource(String, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="116" height="10" title="68" alt="68"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="32" alt="32"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">17</td><td class="ctr2" id="g0">17</td><td class="ctr1" id="h0">16</td><td class="ctr2" id="i0">16</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="RequestUriUtils.java.html#L7" class="el_method">isStaticResource(String)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i2">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="RequestUriUtils.java.html#L29" class="el_method">isTrackableResource(String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="4" alt="4"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="RequestUriUtils.java.html#L3" class="el_method">RequestUriUtils()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="5" height="10" title="3" alt="3"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/RequestUriUtils.java.html b/testResult/html/stirling.software.SPDF.utils/RequestUriUtils.java.html
    new file mode 100644
    index 000000000..52545fbc2
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/RequestUriUtils.java.html
    @@ -0,0 +1,51 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>RequestUriUtils.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">RequestUriUtils.java</span></div><h1>RequestUriUtils.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +<span class="nc" id="L3">public class RequestUriUtils {</span>
    +
    +    public static boolean isStaticResource(String requestURI) {
    +
    +<span class="nc" id="L7">        return isStaticResource(&quot;&quot;, requestURI);</span>
    +    }
    +
    +    public static boolean isStaticResource(String contextPath, String requestURI) {
    +
    +<span class="nc bnc" id="L12" title="All 2 branches missed.">        return requestURI.startsWith(contextPath + &quot;/css/&quot;)</span>
    +<span class="nc bnc" id="L13" title="All 2 branches missed.">                || requestURI.startsWith(contextPath + &quot;/fonts/&quot;)</span>
    +<span class="nc bnc" id="L14" title="All 2 branches missed.">                || requestURI.startsWith(contextPath + &quot;/js/&quot;)</span>
    +<span class="nc bnc" id="L15" title="All 2 branches missed.">                || requestURI.endsWith(contextPath + &quot;robots.txt&quot;)</span>
    +<span class="nc bnc" id="L16" title="All 2 branches missed.">                || requestURI.startsWith(contextPath + &quot;/images/&quot;)</span>
    +<span class="nc bnc" id="L17" title="All 2 branches missed.">                || requestURI.startsWith(contextPath + &quot;/public/&quot;)</span>
    +<span class="nc bnc" id="L18" title="All 2 branches missed.">                || requestURI.startsWith(contextPath + &quot;/pdfjs/&quot;)</span>
    +<span class="nc bnc" id="L19" title="All 2 branches missed.">                || requestURI.startsWith(contextPath + &quot;/login&quot;)</span>
    +<span class="nc bnc" id="L20" title="All 2 branches missed.">                || requestURI.startsWith(contextPath + &quot;/error&quot;)</span>
    +<span class="nc bnc" id="L21" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.svg&quot;)</span>
    +<span class="nc bnc" id="L22" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.png&quot;)</span>
    +<span class="nc bnc" id="L23" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.ico&quot;)</span>
    +<span class="nc bnc" id="L24" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.webmanifest&quot;)</span>
    +<span class="nc bnc" id="L25" title="All 2 branches missed.">                || requestURI.startsWith(contextPath + &quot;/api/v1/info/status&quot;);</span>
    +    }
    +
    +    public static boolean isTrackableResource(String requestURI) {
    +<span class="nc" id="L29">        return isTrackableResource(&quot;&quot;, requestURI);</span>
    +    }
    +
    +    public static boolean isTrackableResource(String contextPath, String requestURI) {
    +<span class="nc bnc" id="L33" title="All 2 branches missed.">        return !(requestURI.startsWith(&quot;/js&quot;)</span>
    +<span class="nc bnc" id="L34" title="All 2 branches missed.">                || requestURI.startsWith(&quot;/v1/api-docs&quot;)</span>
    +<span class="nc bnc" id="L35" title="All 2 branches missed.">                || requestURI.endsWith(&quot;robots.txt&quot;)</span>
    +<span class="nc bnc" id="L36" title="All 2 branches missed.">                || requestURI.startsWith(&quot;/images&quot;)</span>
    +<span class="nc bnc" id="L37" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.png&quot;)</span>
    +<span class="nc bnc" id="L38" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.ico&quot;)</span>
    +<span class="nc bnc" id="L39" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.css&quot;)</span>
    +<span class="nc bnc" id="L40" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.map&quot;)</span>
    +<span class="nc bnc" id="L41" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.svg&quot;)</span>
    +<span class="nc bnc" id="L42" title="All 2 branches missed.">                || requestURI.endsWith(&quot;popularity.txt&quot;)</span>
    +<span class="nc bnc" id="L43" title="All 2 branches missed.">                || requestURI.endsWith(&quot;.js&quot;)</span>
    +<span class="nc bnc" id="L44" title="All 2 branches missed.">                || requestURI.contains(&quot;swagger&quot;)</span>
    +<span class="nc bnc" id="L45" title="All 2 branches missed.">                || requestURI.startsWith(&quot;/api/v1/info&quot;)</span>
    +<span class="nc bnc" id="L46" title="All 2 branches missed.">                || requestURI.startsWith(&quot;/site.webmanifest&quot;)</span>
    +<span class="nc bnc" id="L47" title="All 2 branches missed.">                || requestURI.startsWith(&quot;/fonts&quot;)</span>
    +<span class="nc bnc" id="L48" title="All 2 branches missed.">                || requestURI.startsWith(&quot;/pdfjs&quot;));</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/UIScaling.html b/testResult/html/stirling.software.SPDF.utils/UIScaling.html
    new file mode 100644
    index 000000000..86b07be90
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/UIScaling.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UIScaling</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">UIScaling</span></div><h1>UIScaling</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">130 of 130</td><td class="ctr2">0%</td><td class="bar">4 of 4</td><td class="ctr2">0%</td><td class="ctr1">11</td><td class="ctr2">11</td><td class="ctr1">25</td><td class="ctr2">25</td><td class="ctr1">9</td><td class="ctr2">9</td></tr></tfoot><tbody><tr><td id="a6"><a href="UIScaling.java.html#L49" class="el_method">scaleIcon(Image, int, int)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="58" alt="58"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="4" alt="4"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">3</td><td class="ctr2" id="g0">3</td><td class="ctr1" id="h0">10</td><td class="ctr2" id="i0">10</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a3"><a href="UIScaling.java.html#L34" class="el_method">scale(Insets)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="16" alt="16"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">5</td><td class="ctr2" id="i1">5</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a4"><a href="UIScaling.java.html#L43" class="el_method">scaleFont(Font)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="26" height="10" title="13" alt="13"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">2</td><td class="ctr2" id="i2">2</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a2"><a href="UIScaling.java.html#L30" class="el_method">scale(Dimension)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="10" alt="10"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a1"><a href="UIScaling.java.html#L12" class="el_method">getWidthScaleFactor()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="8" alt="8"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h3">2</td><td class="ctr2" id="i3">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="UIScaling.java.html#L17" class="el_method">getHeightScaleFactor()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="8" alt="8"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a7"><a href="UIScaling.java.html#L22" class="el_method">scaleWidth(int)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="7" alt="7"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">1</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a5"><a href="UIScaling.java.html#L26" class="el_method">scaleHeight(int)</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h7">1</td><td class="ctr2" id="i7">1</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a8"><a href="UIScaling.java.html#L7" class="el_method">UIScaling()</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="3" alt="3"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/UIScaling.java.html b/testResult/html/stirling.software.SPDF.utils/UIScaling.java.html
    new file mode 100644
    index 000000000..0fc3f61b4
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/UIScaling.java.html
    @@ -0,0 +1,68 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UIScaling.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">UIScaling.java</span></div><h1>UIScaling.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.awt.*;
    +
    +import javax.swing.*;
    +
    +<span class="nc" id="L7">public class UIScaling {</span>
    +    private static final double BASE_RESOLUTION_WIDTH = 1920.0;
    +    private static final double BASE_RESOLUTION_HEIGHT = 1080.0;
    +
    +    public static double getWidthScaleFactor() {
    +<span class="nc" id="L12">        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();</span>
    +<span class="nc" id="L13">        return screenSize.getWidth() / BASE_RESOLUTION_WIDTH;</span>
    +    }
    +
    +    public static double getHeightScaleFactor() {
    +<span class="nc" id="L17">        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();</span>
    +<span class="nc" id="L18">        return screenSize.getHeight() / BASE_RESOLUTION_HEIGHT;</span>
    +    }
    +
    +    public static int scaleWidth(int value) {
    +<span class="nc" id="L22">        return (int) Math.round(value * getWidthScaleFactor());</span>
    +    }
    +
    +    public static int scaleHeight(int value) {
    +<span class="nc" id="L26">        return (int) Math.round(value * getHeightScaleFactor());</span>
    +    }
    +
    +    public static Dimension scale(Dimension dim) {
    +<span class="nc" id="L30">        return new Dimension(scaleWidth(dim.width), scaleHeight(dim.height));</span>
    +    }
    +
    +    public static Insets scale(Insets insets) {
    +<span class="nc" id="L34">        return new Insets(</span>
    +<span class="nc" id="L35">                scaleHeight(insets.top),</span>
    +<span class="nc" id="L36">                scaleWidth(insets.left),</span>
    +<span class="nc" id="L37">                scaleHeight(insets.bottom),</span>
    +<span class="nc" id="L38">                scaleWidth(insets.right));</span>
    +    }
    +
    +    public static Font scaleFont(Font font) {
    +        // For fonts, we'll use the smaller scale factor to ensure readability
    +<span class="nc" id="L43">        double scaleFactor = Math.min(getWidthScaleFactor(), getHeightScaleFactor());</span>
    +<span class="nc" id="L44">        return font.deriveFont((float) (font.getSize() * scaleFactor));</span>
    +    }
    +
    +    // Utility method for aspect ratio aware icon scaling
    +    public static Image scaleIcon(Image icon, int targetWidth, int targetHeight) {
    +<span class="nc bnc" id="L49" title="All 2 branches missed.">        if (icon == null) return null;</span>
    +
    +<span class="nc" id="L51">        double widthScale = getWidthScaleFactor();</span>
    +<span class="nc" id="L52">        double heightScale = getHeightScaleFactor();</span>
    +
    +<span class="nc" id="L54">        int scaledWidth = (int) Math.round(targetWidth * widthScale);</span>
    +<span class="nc" id="L55">        int scaledHeight = (int) Math.round(targetHeight * heightScale);</span>
    +
    +        // Maintain aspect ratio for icons
    +<span class="nc" id="L58">        double aspectRatio = (double) icon.getWidth(null) / icon.getHeight(null);</span>
    +<span class="nc bnc" id="L59" title="All 2 branches missed.">        if (scaledWidth / scaledHeight &gt; aspectRatio) {</span>
    +<span class="nc" id="L60">            scaledWidth = (int) (scaledHeight * aspectRatio);</span>
    +        } else {
    +<span class="nc" id="L62">            scaledHeight = (int) (scaledWidth / aspectRatio);</span>
    +        }
    +
    +<span class="nc" id="L65">        return icon.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/UrlUtils.html b/testResult/html/stirling.software.SPDF.utils/UrlUtils.html
    new file mode 100644
    index 000000000..874c8da6d
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/UrlUtils.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UrlUtils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">UrlUtils</span></div><h1>UrlUtils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">43 of 43</td><td class="ctr2">0%</td><td class="bar">2 of 2</td><td class="ctr2">0%</td><td class="ctr1">5</td><td class="ctr2">5</td><td class="ctr1">14</td><td class="ctr2">14</td><td class="ctr1">4</td><td class="ctr2">4</td></tr></tfoot><tbody><tr><td id="a1"><a href="UrlUtils.java.html#L11" class="el_method">getOrigin(HttpServletRequest)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h0">5</td><td class="ctr2" id="i0">5</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a2"><a href="UrlUtils.java.html#L20" class="el_method">isPortAvailable(int)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="12" alt="12"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a0"><a href="UrlUtils.java.html#L28" class="el_method">findAvailablePort(int)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="66" height="10" title="10" alt="10"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="2" alt="2"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">2</td><td class="ctr2" id="g0">2</td><td class="ctr1" id="h2">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a3"><a href="UrlUtils.java.html#L8" class="el_method">UrlUtils()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="3" alt="3"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/UrlUtils.java.html b/testResult/html/stirling.software.SPDF.utils/UrlUtils.java.html
    new file mode 100644
    index 000000000..4e4d75a55
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/UrlUtils.java.html
    @@ -0,0 +1,35 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>UrlUtils.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">UrlUtils.java</span></div><h1>UrlUtils.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.io.IOException;
    +import java.net.ServerSocket;
    +
    +import jakarta.servlet.http.HttpServletRequest;
    +
    +<span class="nc" id="L8">public class UrlUtils {</span>
    +
    +    public static String getOrigin(HttpServletRequest request) {
    +<span class="nc" id="L11">        String scheme = request.getScheme(); // http or https</span>
    +<span class="nc" id="L12">        String serverName = request.getServerName(); // localhost</span>
    +<span class="nc" id="L13">        int serverPort = request.getServerPort(); // 8080</span>
    +<span class="nc" id="L14">        String contextPath = request.getContextPath(); // /myapp</span>
    +
    +<span class="nc" id="L16">        return scheme + &quot;://&quot; + serverName + &quot;:&quot; + serverPort + contextPath;</span>
    +    }
    +
    +    public static boolean isPortAvailable(int port) {
    +<span class="nc" id="L20">        try (ServerSocket socket = new ServerSocket(port)) {</span>
    +<span class="nc" id="L21">            return true;</span>
    +<span class="nc" id="L22">        } catch (IOException e) {</span>
    +<span class="nc" id="L23">            return false;</span>
    +        }
    +    }
    +
    +    public static String findAvailablePort(int startPort) {
    +<span class="nc" id="L28">        int port = startPort;</span>
    +<span class="nc bnc" id="L29" title="All 2 branches missed.">        while (!isPortAvailable(port)) {</span>
    +<span class="nc" id="L30">            port++;</span>
    +        }
    +<span class="nc" id="L32">        return String.valueOf(port);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/WebResponseUtils.html b/testResult/html/stirling.software.SPDF.utils/WebResponseUtils.html
    new file mode 100644
    index 000000000..02e42407b
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/WebResponseUtils.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>WebResponseUtils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_class">WebResponseUtils</span></div><h1>WebResponseUtils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">43 of 79</td><td class="ctr2">45%</td><td class="bar">0 of 0</td><td class="ctr2">n/a</td><td class="ctr1">5</td><td class="ctr2">7</td><td class="ctr1">11</td><td class="ctr2">20</td><td class="ctr1">5</td><td class="ctr2">7</td></tr></tfoot><tbody><tr><td id="a4"><a href="WebResponseUtils.java.html#L31" class="el_method">multiPartFileToWebResponse(MultipartFile)</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="16" alt="16"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d0"/><td class="ctr2" id="e0">n/a</td><td class="ctr1" id="f0">1</td><td class="ctr2" id="g0">1</td><td class="ctr1" id="h0">4</td><td class="ctr2" id="i1">4</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="WebResponseUtils.java.html#L62" class="el_method">pdfDocToWebResponse(PDDocument, String)</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="50" height="10" title="13" alt="13"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"/><td class="ctr2" id="e1">n/a</td><td class="ctr1" id="f1">1</td><td class="ctr2" id="g1">1</td><td class="ctr1" id="h1">4</td><td class="ctr2" id="i2">4</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="WebResponseUtils.java.html#L26" class="el_method">boasToWebResponse(ByteArrayOutputStream, String, MediaType)</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="23" height="10" title="6" alt="6"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d2"/><td class="ctr2" id="e2">n/a</td><td class="ctr1" id="f2">1</td><td class="ctr2" id="g2">1</td><td class="ctr1" id="h2">1</td><td class="ctr2" id="i3">1</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a0"><a href="WebResponseUtils.java.html#L21" class="el_method">boasToWebResponse(ByteArrayOutputStream, String)</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="19" height="10" title="5" alt="5"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h3">1</td><td class="ctr2" id="i4">1</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a6"><a href="WebResponseUtils.java.html#L17" class="el_method">WebResponseUtils()</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="11" height="10" title="3" alt="3"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a3"><a href="WebResponseUtils.java.html#L43" class="el_method">bytesToWebResponse(byte[], String, MediaType)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/greenbar.gif" width="120" height="10" title="31" alt="31"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">0</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">0</td><td class="ctr2" id="i0">8</td><td class="ctr1" id="j5">0</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a2"><a href="WebResponseUtils.java.html#L55" class="el_method">bytesToWebResponse(byte[], String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/greenbar.gif" width="19" height="10" title="5" alt="5"/></td><td class="ctr2" id="c1">100%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">0</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h6">0</td><td class="ctr2" id="i6">1</td><td class="ctr1" id="j6">0</td><td class="ctr2" id="k6">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/WebResponseUtils.java.html b/testResult/html/stirling.software.SPDF.utils/WebResponseUtils.java.html
    new file mode 100644
    index 000000000..e4f386a7f
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/WebResponseUtils.java.html
    @@ -0,0 +1,70 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>WebResponseUtils.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF.utils</a> &gt; <span class="el_source">WebResponseUtils.java</span></div><h1>WebResponseUtils.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF.utils;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.net.URLEncoder;
    +import java.nio.charset.StandardCharsets;
    +
    +import org.apache.pdfbox.pdmodel.PDDocument;
    +import org.springframework.http.HttpHeaders;
    +import org.springframework.http.HttpStatus;
    +import org.springframework.http.MediaType;
    +import org.springframework.http.ResponseEntity;
    +import org.springframework.web.multipart.MultipartFile;
    +
    +import io.github.pixee.security.Filenames;
    +
    +<span class="nc" id="L17">public class WebResponseUtils {</span>
    +
    +    public static ResponseEntity&lt;byte[]&gt; boasToWebResponse(
    +            ByteArrayOutputStream baos, String docName) throws IOException {
    +<span class="nc" id="L21">        return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName);</span>
    +    }
    +
    +    public static ResponseEntity&lt;byte[]&gt; boasToWebResponse(
    +            ByteArrayOutputStream baos, String docName, MediaType mediaType) throws IOException {
    +<span class="nc" id="L26">        return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), docName, mediaType);</span>
    +    }
    +
    +    public static ResponseEntity&lt;byte[]&gt; multiPartFileToWebResponse(MultipartFile file)
    +            throws IOException {
    +<span class="nc" id="L31">        String fileName = Filenames.toSimpleFileName(file.getOriginalFilename());</span>
    +<span class="nc" id="L32">        MediaType mediaType = MediaType.parseMediaType(file.getContentType());</span>
    +
    +<span class="nc" id="L34">        byte[] bytes = file.getBytes();</span>
    +
    +<span class="nc" id="L36">        return bytesToWebResponse(bytes, fileName, mediaType);</span>
    +    }
    +
    +    public static ResponseEntity&lt;byte[]&gt; bytesToWebResponse(
    +            byte[] bytes, String docName, MediaType mediaType) throws IOException {
    +
    +        // Return the PDF as a response
    +<span class="fc" id="L43">        HttpHeaders headers = new HttpHeaders();</span>
    +<span class="fc" id="L44">        headers.setContentType(mediaType);</span>
    +<span class="fc" id="L45">        headers.setContentLength(bytes.length);</span>
    +<span class="fc" id="L46">        String encodedDocName =</span>
    +<span class="fc" id="L47">                URLEncoder.encode(docName, StandardCharsets.UTF_8.toString())</span>
    +<span class="fc" id="L48">                        .replaceAll(&quot;\\+&quot;, &quot;%20&quot;);</span>
    +<span class="fc" id="L49">        headers.setContentDispositionFormData(&quot;attachment&quot;, encodedDocName);</span>
    +<span class="fc" id="L50">        return new ResponseEntity&lt;&gt;(bytes, headers, HttpStatus.OK);</span>
    +    }
    +
    +    public static ResponseEntity&lt;byte[]&gt; bytesToWebResponse(byte[] bytes, String docName)
    +            throws IOException {
    +<span class="fc" id="L55">        return bytesToWebResponse(bytes, docName, MediaType.APPLICATION_PDF);</span>
    +    }
    +
    +    public static ResponseEntity&lt;byte[]&gt; pdfDocToWebResponse(PDDocument document, String docName)
    +            throws IOException {
    +
    +        // Open Byte Array and save document to it
    +<span class="nc" id="L62">        ByteArrayOutputStream baos = new ByteArrayOutputStream();</span>
    +<span class="nc" id="L63">        document.save(baos);</span>
    +        // Close the document
    +<span class="nc" id="L65">        document.close();</span>
    +
    +<span class="nc" id="L67">        return boasToWebResponse(baos, docName);</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/index.html b/testResult/html/stirling.software.SPDF.utils/index.html
    new file mode 100644
    index 000000000..90fd720ef
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.utils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.utils</span></div><h1>stirling.software.SPDF.utils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4,466 of 5,042</td><td class="ctr2">11%</td><td class="bar">500 of 532</td><td class="ctr2">6%</td><td class="ctr1">402</td><td class="ctr2">424</td><td class="ctr1">1,081</td><td class="ctr2">1,229</td><td class="ctr1">130</td><td class="ctr2">145</td><td class="ctr1">17</td><td class="ctr2">22</td></tr></tfoot><tbody><tr><td id="a11"><a href="PdfUtils.html" class="el_class">PdfUtils</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,115" alt="1,115"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="117" height="10" title="116" alt="116"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">80</td><td class="ctr2" id="g1">80</td><td class="ctr1" id="h0">258</td><td class="ctr2" id="i0">258</td><td class="ctr1" id="j1">17</td><td class="ctr2" id="k1">17</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a7"><a href="GeneralUtils.html" class="el_class">GeneralUtils</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="100" height="10" title="933" alt="933"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="118" alt="118"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f0">84</td><td class="ctr2" id="g0">84</td><td class="ctr1" id="h1">207</td><td class="ctr2" id="i1">207</td><td class="ctr1" id="j0">25</td><td class="ctr2" id="k0">25</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr><tr><td id="a10"><a href="PDFToFile.html" class="el_class">PDFToFile</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="54" height="10" title="502" alt="502"/><img src="../jacoco-resources/greenbar.gif" width="23" height="10" title="221" alt="221"/></td><td class="ctr2" id="c4">30%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="40" alt="40"/><img src="../jacoco-resources/greenbar.gif" width="10" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">20%</td><td class="ctr1" id="f6">24</td><td class="ctr2" id="g5">30</td><td class="ctr1" id="h2">107</td><td class="ctr2" id="i2">159</td><td class="ctr1" id="j17">2</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l17">0</td><td class="ctr2" id="m2">1</td></tr><tr><td id="a5"><a href="FileToPdf.html" class="el_class">FileToPdf</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="420" alt="420"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="38" alt="38"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">30</td><td class="ctr2" id="g6">30</td><td class="ctr1" id="h3">104</td><td class="ctr2" id="i4">104</td><td class="ctr1" id="j2">11</td><td class="ctr2" id="k2">11</td><td class="ctr1" id="l2">1</td><td class="ctr2" id="m3">1</td></tr><tr><td id="a4"><a href="FileMonitor.html" class="el_class">FileMonitor</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="39" height="10" title="369" alt="369"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="40" alt="40"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f3">31</td><td class="ctr2" id="g4">31</td><td class="ctr1" id="h4">93</td><td class="ctr2" id="i5">93</td><td class="ctr1" id="j3">11</td><td class="ctr2" id="k3">11</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a9"><a href="ImageProcessingUtils.html" class="el_class">ImageProcessingUtils</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="25" height="10" title="234" alt="234"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="20" alt="20"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f7">19</td><td class="ctr2" id="g7">19</td><td class="ctr1" id="h6">61</td><td class="ctr2" id="i6">61</td><td class="ctr1" id="j5">7</td><td class="ctr2" id="k6">7</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a18"><a href="RequestUriUtils.html" class="el_class">RequestUriUtils</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="149" alt="149"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="60" alt="60"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f2">35</td><td class="ctr2" id="g3">35</td><td class="ctr1" id="h7">33</td><td class="ctr2" id="i7">33</td><td class="ctr1" id="j6">5</td><td class="ctr2" id="k9">5</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a14"><a href="ProcessExecutor.html" class="el_class">ProcessExecutor</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="14" height="10" title="139" alt="139"/><img src="../jacoco-resources/greenbar.gif" width="27" height="10" title="256" alt="256"/></td><td class="ctr2" id="c1">64%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="32" alt="32"/><img src="../jacoco-resources/greenbar.gif" width="22" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">40%</td><td class="ctr1" id="f5">30</td><td class="ctr2" id="g2">42</td><td class="ctr1" id="h5">75</td><td class="ctr2" id="i3">149</td><td class="ctr1" id="j18">1</td><td class="ctr2" id="k4">9</td><td class="ctr1" id="l18">0</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a19"><a href="UIScaling.html" class="el_class">UIScaling</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="130" alt="130"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f9">11</td><td class="ctr2" id="g9">11</td><td class="ctr1" id="h8">25</td><td class="ctr2" id="i8">25</td><td class="ctr1" id="j4">9</td><td class="ctr2" id="k5">9</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a3"><a href="FileInfo.html" class="el_class">FileInfo</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="91" alt="91"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="6" alt="6"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f10">8</td><td class="ctr2" id="g10">8</td><td class="ctr1" id="h13">12</td><td class="ctr2" id="i14">12</td><td class="ctr1" id="j7">5</td><td class="ctr2" id="k10">5</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a17"><a href="PropertyConfigs.html" class="el_class">PropertyConfigs</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="85" alt="85"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="20" alt="20"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f8">15</td><td class="ctr2" id="g8">15</td><td class="ctr1" id="h10">19</td><td class="ctr2" id="i11">19</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k11">5</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a0"><a href="CheckProgramInstall.html" class="el_class">CheckProgramInstall</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="61" alt="61"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f11">7</td><td class="ctr2" id="g11">7</td><td class="ctr1" id="h9">20</td><td class="ctr2" id="i9">20</td><td class="ctr1" id="j9">5</td><td class="ctr2" id="k12">5</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a2"><a href="ErrorUtils.html" class="el_class">ErrorUtils</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="59" alt="59"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f15">3</td><td class="ctr2" id="g15">3</td><td class="ctr1" id="h11">14</td><td class="ctr2" id="i12">14</td><td class="ctr1" id="j13">3</td><td class="ctr2" id="k15">3</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a21"><a href="WebResponseUtils.html" class="el_class">WebResponseUtils</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="43" alt="43"/><img src="../jacoco-resources/greenbar.gif" width="3" height="10" title="36" alt="36"/></td><td class="ctr2" id="c3">45%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f12">5</td><td class="ctr2" id="g12">7</td><td class="ctr1" id="h14">11</td><td class="ctr2" id="i10">20</td><td class="ctr1" id="j10">5</td><td class="ctr2" id="k7">7</td><td class="ctr1" id="l19">0</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a20"><a href="UrlUtils.html" class="el_class">UrlUtils</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="43" alt="43"/></td><td class="ctr2" id="c16">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f13">5</td><td class="ctr2" id="g13">5</td><td class="ctr1" id="h12">14</td><td class="ctr2" id="i13">14</td><td class="ctr1" id="j11">4</td><td class="ctr2" id="k14">4</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a1"><a href="CustomHtmlSanitizer.html" class="el_class">CustomHtmlSanitizer</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="34" alt="34"/></td><td class="ctr2" id="c17">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f16">3</td><td class="ctr2" id="g16">3</td><td class="ctr1" id="h15">10</td><td class="ctr2" id="i15">10</td><td class="ctr1" id="j14">3</td><td class="ctr2" id="k16">3</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m15">1</td></tr><tr><td id="a16"><a href="ProcessExecutor$ProcessExecutorResult.html" class="el_class">ProcessExecutor.ProcessExecutorResult</a></td><td class="bar" id="b16"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="14" alt="14"/><img src="../jacoco-resources/greenbar.gif" width="1" height="10" title="12" alt="12"/></td><td class="ctr2" id="c2">46%</td><td class="bar" id="d16"/><td class="ctr2" id="e16">n/a</td><td class="ctr1" id="f14">4</td><td class="ctr2" id="g14">5</td><td class="ctr1" id="h16">6</td><td class="ctr2" id="i16">10</td><td class="ctr1" id="j12">4</td><td class="ctr2" id="k13">5</td><td class="ctr1" id="l20">0</td><td class="ctr2" id="m16">1</td></tr><tr><td id="a8"><a href="GeneralUtils$1.html" class="el_class">GeneralUtils.new SimpleFileVisitor() {...}</a></td><td class="bar" id="b17"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="13" alt="13"/></td><td class="ctr2" id="c18">0%</td><td class="bar" id="d17"/><td class="ctr2" id="e17">n/a</td><td class="ctr1" id="f17">3</td><td class="ctr2" id="g17">3</td><td class="ctr1" id="h17">5</td><td class="ctr2" id="i18">5</td><td class="ctr1" id="j15">3</td><td class="ctr2" id="k17">3</td><td class="ctr1" id="l13">1</td><td class="ctr2" id="m17">1</td></tr><tr><td id="a13"><a href="PdfUtils$PdfRenderSettingsKey.html" class="el_class">PdfUtils.PdfRenderSettingsKey</a></td><td class="bar" id="b18"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="12" alt="12"/></td><td class="ctr2" id="c19">0%</td><td class="bar" id="d18"/><td class="ctr2" id="e18">n/a</td><td class="ctr1" id="f19">1</td><td class="ctr2" id="g19">1</td><td class="ctr1" id="h19">1</td><td class="ctr2" id="i20">1</td><td class="ctr1" id="j19">1</td><td class="ctr2" id="k19">1</td><td class="ctr1" id="l14">1</td><td class="ctr2" id="m18">1</td></tr><tr><td id="a6"><a href="FileToPdf$1.html" class="el_class">FileToPdf.new SimpleFileVisitor() {...}</a></td><td class="bar" id="b19"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="11" alt="11"/></td><td class="ctr2" id="c20">0%</td><td class="bar" id="d19"/><td class="ctr2" id="e19">n/a</td><td class="ctr1" id="f18">3</td><td class="ctr2" id="g18">3</td><td class="ctr1" id="h18">5</td><td class="ctr2" id="i19">5</td><td class="ctr1" id="j16">3</td><td class="ctr2" id="k18">3</td><td class="ctr1" id="l15">1</td><td class="ctr2" id="m19">1</td></tr><tr><td id="a12"><a href="PdfUtils$PdfImageDimensionValue.html" class="el_class">PdfUtils.PdfImageDimensionValue</a></td><td class="bar" id="b20"/><td class="ctr2" id="c21">0%</td><td class="bar" id="d20"/><td class="ctr2" id="e20">n/a</td><td class="ctr1" id="f20">1</td><td class="ctr2" id="g20">1</td><td class="ctr1" id="h20">1</td><td class="ctr2" id="i21">1</td><td class="ctr1" id="j20">1</td><td class="ctr2" id="k20">1</td><td class="ctr1" id="l16">1</td><td class="ctr2" id="m20">1</td></tr><tr><td id="a15"><a href="ProcessExecutor$Processes.html" class="el_class">ProcessExecutor.Processes</a></td><td class="bar" id="b21"><img src="../jacoco-resources/greenbar.gif" width="5" height="10" title="51" alt="51"/></td><td class="ctr2" id="c0">100%</td><td class="bar" id="d21"/><td class="ctr2" id="e21">n/a</td><td class="ctr1" id="f21">0</td><td class="ctr2" id="g21">1</td><td class="ctr1" id="h21">0</td><td class="ctr2" id="i17">9</td><td class="ctr1" id="j21">0</td><td class="ctr2" id="k21">1</td><td class="ctr1" id="l21">0</td><td class="ctr2" id="m21">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF.utils/index.source.html b/testResult/html/stirling.software.SPDF.utils/index.source.html
    new file mode 100644
    index 000000000..6cbd91f84
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF.utils/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF.utils</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF.utils</span></div><h1>stirling.software.SPDF.utils</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">4,466 of 5,042</td><td class="ctr2">11%</td><td class="bar">500 of 532</td><td class="ctr2">6%</td><td class="ctr1">402</td><td class="ctr2">424</td><td class="ctr1">1,081</td><td class="ctr2">1,229</td><td class="ctr1">130</td><td class="ctr2">145</td><td class="ctr1">17</td><td class="ctr2">22</td></tr></tfoot><tbody><tr><td id="a9"><a href="PdfUtils.java.html" class="el_source">PdfUtils.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="1,136" alt="1,136"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="117" height="10" title="116" alt="116"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">82</td><td class="ctr2" id="g1">82</td><td class="ctr1" id="h0">260</td><td class="ctr2" id="i0">260</td><td class="ctr1" id="j1">19</td><td class="ctr2" id="k1">19</td><td class="ctr1" id="l0">3</td><td class="ctr2" id="m0">3</td></tr><tr><td id="a6"><a href="GeneralUtils.java.html" class="el_source">GeneralUtils.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="99" height="10" title="946" alt="946"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="118" alt="118"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f0">87</td><td class="ctr2" id="g0">87</td><td class="ctr1" id="h1">212</td><td class="ctr2" id="i1">212</td><td class="ctr1" id="j0">28</td><td class="ctr2" id="k0">28</td><td class="ctr1" id="l1">2</td><td class="ctr2" id="m2">2</td></tr><tr><td id="a8"><a href="PDFToFile.java.html" class="el_source">PDFToFile.java</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="53" height="10" title="502" alt="502"/><img src="../jacoco-resources/greenbar.gif" width="23" height="10" title="221" alt="221"/></td><td class="ctr2" id="c2">30%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="40" alt="40"/><img src="../jacoco-resources/greenbar.gif" width="10" height="10" title="10" alt="10"/></td><td class="ctr2" id="e1">20%</td><td class="ctr1" id="f6">24</td><td class="ctr2" id="g6">30</td><td class="ctr1" id="h3">107</td><td class="ctr2" id="i3">159</td><td class="ctr1" id="j15">2</td><td class="ctr2" id="k8">5</td><td class="ctr1" id="l13">0</td><td class="ctr2" id="m4">1</td></tr><tr><td id="a5"><a href="FileToPdf.java.html" class="el_source">FileToPdf.java</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="45" height="10" title="431" alt="431"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="38" alt="38"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">33</td><td class="ctr2" id="g4">33</td><td class="ctr1" id="h2">109</td><td class="ctr2" id="i4">109</td><td class="ctr1" id="j2">14</td><td class="ctr2" id="k3">14</td><td class="ctr1" id="l2">2</td><td class="ctr2" id="m3">2</td></tr><tr><td id="a4"><a href="FileMonitor.java.html" class="el_source">FileMonitor.java</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="38" height="10" title="369" alt="369"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="40" alt="40"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">31</td><td class="ctr2" id="g5">31</td><td class="ctr1" id="h4">93</td><td class="ctr2" id="i5">93</td><td class="ctr1" id="j3">11</td><td class="ctr2" id="k4">11</td><td class="ctr1" id="l3">1</td><td class="ctr2" id="m5">1</td></tr><tr><td id="a7"><a href="ImageProcessingUtils.java.html" class="el_source">ImageProcessingUtils.java</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="24" height="10" title="234" alt="234"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d7"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="20" alt="20"/></td><td class="ctr2" id="e6">0%</td><td class="ctr1" id="f7">19</td><td class="ctr2" id="g7">19</td><td class="ctr1" id="h6">61</td><td class="ctr2" id="i6">61</td><td class="ctr1" id="j5">7</td><td class="ctr2" id="k6">7</td><td class="ctr1" id="l4">1</td><td class="ctr2" id="m6">1</td></tr><tr><td id="a10"><a href="ProcessExecutor.java.html" class="el_source">ProcessExecutor.java</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="16" height="10" title="153" alt="153"/><img src="../jacoco-resources/greenbar.gif" width="33" height="10" title="319" alt="319"/></td><td class="ctr2" id="c0">67%</td><td class="bar" id="d6"><img src="../jacoco-resources/redbar.gif" width="32" height="10" title="32" alt="32"/><img src="../jacoco-resources/greenbar.gif" width="22" height="10" title="22" alt="22"/></td><td class="ctr2" id="e0">40%</td><td class="ctr1" id="f3">34</td><td class="ctr2" id="g2">48</td><td class="ctr1" id="h5">81</td><td class="ctr2" id="i2">168</td><td class="ctr1" id="j6">5</td><td class="ctr2" id="k2">15</td><td class="ctr1" id="l14">0</td><td class="ctr2" id="m1">3</td></tr><tr><td id="a12"><a href="RequestUriUtils.java.html" class="el_source">RequestUriUtils.java</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="15" height="10" title="149" alt="149"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="61" height="10" title="60" alt="60"/></td><td class="ctr2" id="e7">0%</td><td class="ctr1" id="f2">35</td><td class="ctr2" id="g3">35</td><td class="ctr1" id="h7">33</td><td class="ctr2" id="i7">33</td><td class="ctr1" id="j7">5</td><td class="ctr2" id="k9">5</td><td class="ctr1" id="l5">1</td><td class="ctr2" id="m7">1</td></tr><tr><td id="a13"><a href="UIScaling.java.html" class="el_source">UIScaling.java</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="130" alt="130"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d10"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e8">0%</td><td class="ctr1" id="f9">11</td><td class="ctr2" id="g9">11</td><td class="ctr1" id="h8">25</td><td class="ctr2" id="i8">25</td><td class="ctr1" id="j4">9</td><td class="ctr2" id="k5">9</td><td class="ctr1" id="l6">1</td><td class="ctr2" id="m8">1</td></tr><tr><td id="a3"><a href="FileInfo.java.html" class="el_source">FileInfo.java</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="9" height="10" title="91" alt="91"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d9"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="6" alt="6"/></td><td class="ctr2" id="e9">0%</td><td class="ctr1" id="f10">8</td><td class="ctr2" id="g10">8</td><td class="ctr1" id="h13">12</td><td class="ctr2" id="i14">12</td><td class="ctr1" id="j8">5</td><td class="ctr2" id="k10">5</td><td class="ctr1" id="l7">1</td><td class="ctr2" id="m9">1</td></tr><tr><td id="a11"><a href="PropertyConfigs.java.html" class="el_source">PropertyConfigs.java</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="8" height="10" title="85" alt="85"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d8"><img src="../jacoco-resources/redbar.gif" width="20" height="10" title="20" alt="20"/></td><td class="ctr2" id="e10">0%</td><td class="ctr1" id="f8">15</td><td class="ctr2" id="g8">15</td><td class="ctr1" id="h10">19</td><td class="ctr2" id="i11">19</td><td class="ctr1" id="j9">5</td><td class="ctr2" id="k11">5</td><td class="ctr1" id="l8">1</td><td class="ctr2" id="m10">1</td></tr><tr><td id="a0"><a href="CheckProgramInstall.java.html" class="el_source">CheckProgramInstall.java</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="61" alt="61"/></td><td class="ctr2" id="c12">0%</td><td class="bar" id="d11"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="4" alt="4"/></td><td class="ctr2" id="e11">0%</td><td class="ctr1" id="f11">7</td><td class="ctr2" id="g11">7</td><td class="ctr1" id="h9">20</td><td class="ctr2" id="i9">20</td><td class="ctr1" id="j10">5</td><td class="ctr2" id="k12">5</td><td class="ctr1" id="l9">1</td><td class="ctr2" id="m11">1</td></tr><tr><td id="a2"><a href="ErrorUtils.java.html" class="el_source">ErrorUtils.java</a></td><td class="bar" id="b12"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="59" alt="59"/></td><td class="ctr2" id="c13">0%</td><td class="bar" id="d13"/><td class="ctr2" id="e13">n/a</td><td class="ctr1" id="f14">3</td><td class="ctr2" id="g14">3</td><td class="ctr1" id="h11">14</td><td class="ctr2" id="i12">14</td><td class="ctr1" id="j13">3</td><td class="ctr2" id="k14">3</td><td class="ctr1" id="l10">1</td><td class="ctr2" id="m12">1</td></tr><tr><td id="a15"><a href="WebResponseUtils.java.html" class="el_source">WebResponseUtils.java</a></td><td class="bar" id="b13"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="43" alt="43"/><img src="../jacoco-resources/greenbar.gif" width="3" height="10" title="36" alt="36"/></td><td class="ctr2" id="c1">45%</td><td class="bar" id="d14"/><td class="ctr2" id="e14">n/a</td><td class="ctr1" id="f12">5</td><td class="ctr2" id="g12">7</td><td class="ctr1" id="h14">11</td><td class="ctr2" id="i10">20</td><td class="ctr1" id="j11">5</td><td class="ctr2" id="k7">7</td><td class="ctr1" id="l15">0</td><td class="ctr2" id="m13">1</td></tr><tr><td id="a14"><a href="UrlUtils.java.html" class="el_source">UrlUtils.java</a></td><td class="bar" id="b14"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="43" alt="43"/></td><td class="ctr2" id="c14">0%</td><td class="bar" id="d12"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="2" alt="2"/></td><td class="ctr2" id="e12">0%</td><td class="ctr1" id="f13">5</td><td class="ctr2" id="g13">5</td><td class="ctr1" id="h12">14</td><td class="ctr2" id="i13">14</td><td class="ctr1" id="j12">4</td><td class="ctr2" id="k13">4</td><td class="ctr1" id="l11">1</td><td class="ctr2" id="m14">1</td></tr><tr><td id="a1"><a href="CustomHtmlSanitizer.java.html" class="el_source">CustomHtmlSanitizer.java</a></td><td class="bar" id="b15"><img src="../jacoco-resources/redbar.gif" width="3" height="10" title="34" alt="34"/></td><td class="ctr2" id="c15">0%</td><td class="bar" id="d15"/><td class="ctr2" id="e15">n/a</td><td class="ctr1" id="f15">3</td><td class="ctr2" id="g15">3</td><td class="ctr1" id="h15">10</td><td class="ctr2" id="i15">10</td><td class="ctr1" id="j14">3</td><td class="ctr2" id="k15">3</td><td class="ctr1" id="l12">1</td><td class="ctr2" id="m15">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF/LibreOfficeListener.html b/testResult/html/stirling.software.SPDF/LibreOfficeListener.html
    new file mode 100644
    index 000000000..6576aa710
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF/LibreOfficeListener.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LibreOfficeListener</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF</a> &gt; <span class="el_class">LibreOfficeListener</span></div><h1>LibreOfficeListener</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">123 of 123</td><td class="ctr2">0%</td><td class="bar">14 of 14</td><td class="ctr2">0%</td><td class="ctr1">13</td><td class="ctr2">13</td><td class="ctr1">42</td><td class="ctr2">42</td><td class="ctr1">6</td><td class="ctr2">6</td></tr></tfoot><tbody><tr><td id="a3"><a href="LibreOfficeListener.java.html#L43" class="el_method">start()</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="54" alt="54"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="8" alt="8"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">5</td><td class="ctr2" id="g0">5</td><td class="ctr1" id="h0">18</td><td class="ctr2" id="i0">18</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a1"><a href="LibreOfficeListener.java.html#L31" class="el_method">isListenerRunning()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="22" alt="22"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d3"/><td class="ctr2" id="e3">n/a</td><td class="ctr1" id="f3">1</td><td class="ctr2" id="g3">1</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a2"><a href="LibreOfficeListener.java.html#L56" class="el_method">lambda$start$0()</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="48" height="10" title="22" alt="22"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="30" height="10" title="2" alt="2"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f2">2</td><td class="ctr2" id="g2">2</td><td class="ctr1" id="h1">11</td><td class="ctr2" id="i1">11</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a5"><a href="LibreOfficeListener.java.html#L91" class="el_method">stop()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="33" height="10" title="15" alt="15"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="60" height="10" title="4" alt="4"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f1">3</td><td class="ctr2" id="g1">3</td><td class="ctr1" id="h3">4</td><td class="ctr2" id="i3">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a4"><a href="LibreOfficeListener.java.html#L13" class="el_method">static {...}</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="17" height="10" title="8" alt="8"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d4"/><td class="ctr2" id="e4">n/a</td><td class="ctr1" id="f4">1</td><td class="ctr2" id="g4">1</td><td class="ctr1" id="h4">2</td><td class="ctr2" id="i4">2</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a0"><a href="LibreOfficeListener.java.html#L27" class="el_method">getInstance()</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="2" alt="2"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d5"/><td class="ctr2" id="e5">n/a</td><td class="ctr1" id="f5">1</td><td class="ctr2" id="g5">1</td><td class="ctr1" id="h5">1</td><td class="ctr2" id="i5">1</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF/LibreOfficeListener.java.html b/testResult/html/stirling.software.SPDF/LibreOfficeListener.java.html
    new file mode 100644
    index 000000000..fa4d002b7
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF/LibreOfficeListener.java.html
    @@ -0,0 +1,99 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>LibreOfficeListener.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF</a> &gt; <span class="el_source">LibreOfficeListener.java</span></div><h1>LibreOfficeListener.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF;
    +
    +import java.io.IOException;
    +import java.net.InetSocketAddress;
    +import java.net.Socket;
    +import java.util.concurrent.ExecutorService;
    +import java.util.concurrent.Executors;
    +
    +import io.github.pixee.security.SystemCommand;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +<span class="nc" id="L13">@Slf4j</span>
    +public class LibreOfficeListener {
    +
    +    private static final long ACTIVITY_TIMEOUT = 20L * 60 * 1000; // 20 minutes
    +
    +<span class="nc" id="L18">    private static final LibreOfficeListener INSTANCE = new LibreOfficeListener();</span>
    +    private static final int LISTENER_PORT = 2002;
    +    private ExecutorService executorService;
    +    private long lastActivityTime;
    +    private Process process;
    +
    +    private LibreOfficeListener() {}
    +
    +    public static LibreOfficeListener getInstance() {
    +<span class="nc" id="L27">        return INSTANCE;</span>
    +    }
    +
    +    private boolean isListenerRunning() {
    +<span class="nc" id="L31">        log.info(&quot;waiting for listener to start&quot;);</span>
    +<span class="nc" id="L32">        try (Socket socket = new Socket()) {</span>
    +<span class="nc" id="L33">            socket.connect(</span>
    +                    new InetSocketAddress(&quot;localhost&quot;, 2002), 1000); // Timeout after 1 second
    +<span class="nc" id="L35">            return true;</span>
    +<span class="nc" id="L36">        } catch (Exception e) {</span>
    +<span class="nc" id="L37">            return false;</span>
    +        }
    +    }
    +
    +    public void start() throws IOException {
    +        // Check if the listener is already running
    +<span class="nc bnc" id="L43" title="All 4 branches missed.">        if (process != null &amp;&amp; process.isAlive()) {</span>
    +<span class="nc" id="L44">            return;</span>
    +        }
    +
    +        // Start the listener process
    +<span class="nc" id="L48">        process = SystemCommand.runCommand(Runtime.getRuntime(), &quot;unoconv --listener&quot;);</span>
    +<span class="nc" id="L49">        lastActivityTime = System.currentTimeMillis();</span>
    +
    +        // Start a background thread to monitor the activity timeout
    +<span class="nc" id="L52">        executorService = Executors.newSingleThreadExecutor();</span>
    +<span class="nc" id="L53">        executorService.submit(</span>
    +                () -&gt; {
    +                    while (true) {
    +<span class="nc" id="L56">                        long idleTime = System.currentTimeMillis() - lastActivityTime;</span>
    +<span class="nc bnc" id="L57" title="All 2 branches missed.">                        if (idleTime &gt;= ACTIVITY_TIMEOUT) {</span>
    +                            // If there has been no activity for too long, tear down the listener
    +<span class="nc" id="L59">                            process.destroy();</span>
    +<span class="nc" id="L60">                            break;</span>
    +                        }
    +                        try {
    +<span class="nc" id="L63">                            Thread.sleep(5000); // Check for inactivity every 5 seconds</span>
    +<span class="nc" id="L64">                        } catch (InterruptedException e) {</span>
    +<span class="nc" id="L65">                            Thread.currentThread().interrupt();</span>
    +<span class="nc" id="L66">                            break;</span>
    +<span class="nc" id="L67">                        }</span>
    +<span class="nc" id="L68">                    }</span>
    +<span class="nc" id="L69">                });</span>
    +
    +        // Wait for the listener to start up
    +<span class="nc" id="L72">        long startTime = System.currentTimeMillis();</span>
    +<span class="nc" id="L73">        long timeout = 30000; // Timeout after 30 seconds</span>
    +<span class="nc bnc" id="L74" title="All 2 branches missed.">        while (System.currentTimeMillis() - startTime &lt; timeout) {</span>
    +<span class="nc bnc" id="L75" title="All 2 branches missed.">            if (isListenerRunning()) {</span>
    +
    +<span class="nc" id="L77">                lastActivityTime = System.currentTimeMillis();</span>
    +<span class="nc" id="L78">                return;</span>
    +            }
    +            try {
    +<span class="nc" id="L81">                Thread.sleep(1000);</span>
    +<span class="nc" id="L82">            } catch (InterruptedException e) {</span>
    +<span class="nc" id="L83">                Thread.currentThread().interrupt();</span>
    +<span class="nc" id="L84">                log.error(&quot;exception&quot;, e);</span>
    +<span class="nc" id="L85">            } // Check every 1 second</span>
    +        }
    +<span class="nc" id="L87">    }</span>
    +
    +    public synchronized void stop() {
    +        // Stop the activity timeout monitor thread
    +<span class="nc" id="L91">        executorService.shutdownNow();</span>
    +
    +        // Stop the listener process
    +<span class="nc bnc" id="L94" title="All 4 branches missed.">        if (process != null &amp;&amp; process.isAlive()) {</span>
    +<span class="nc" id="L95">            process.destroy();</span>
    +        }
    +<span class="nc" id="L97">    }</span>
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF/SPDFApplication.html b/testResult/html/stirling.software.SPDF/SPDFApplication.html
    new file mode 100644
    index 000000000..367b1e4f6
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF/SPDFApplication.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SPDFApplication</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.html" class="el_package">stirling.software.SPDF</a> &gt; <span class="el_class">SPDFApplication</span></div><h1>SPDFApplication</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">402 of 402</td><td class="ctr2">0%</td><td class="bar">42 of 42</td><td class="ctr2">0%</td><td class="ctr1">33</td><td class="ctr2">33</td><td class="ctr1">105</td><td class="ctr2">105</td><td class="ctr1">12</td><td class="ctr2">12</td></tr></tfoot><tbody><tr><td id="a6"><a href="SPDFApplication.java.html#L62" class="el_method">main(String[])</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="196" alt="196"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="80" height="10" title="12" alt="12"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f1">7</td><td class="ctr2" id="g1">7</td><td class="ctr1" id="h0">53</td><td class="ctr2" id="i0">53</td><td class="ctr1" id="j0">1</td><td class="ctr2" id="k0">1</td></tr><tr><td id="a5"><a href="SPDFApplication.java.html#L144" class="el_method">init()</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="58" height="10" title="96" alt="96"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="18" alt="18"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f0">10</td><td class="ctr2" id="g0">10</td><td class="ctr1" id="h1">22</td><td class="ctr2" id="i1">22</td><td class="ctr1" id="j1">1</td><td class="ctr2" id="k1">1</td></tr><tr><td id="a1"><a href="SPDFApplication.java.html#L208" class="el_method">getActiveProfile(String[])</a></td><td class="bar" id="b2"><img src="../jacoco-resources/redbar.gif" width="29" height="10" title="48" alt="48"/></td><td class="ctr2" id="c2">0%</td><td class="bar" id="d2"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="6" alt="6"/></td><td class="ctr2" id="e2">0%</td><td class="ctr1" id="f2">4</td><td class="ctr2" id="g2">4</td><td class="ctr1" id="h2">6</td><td class="ctr2" id="i2">6</td><td class="ctr1" id="j2">1</td><td class="ctr2" id="k2">1</td></tr><tr><td id="a7"><a href="SPDFApplication.java.html#L202" class="el_method">printStartupLogs()</a></td><td class="bar" id="b3"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="13" alt="13"/></td><td class="ctr2" id="c3">0%</td><td class="bar" id="d6"/><td class="ctr2" id="e6">n/a</td><td class="ctr1" id="f6">1</td><td class="ctr2" id="g6">1</td><td class="ctr1" id="h4">4</td><td class="ctr2" id="i4">4</td><td class="ctr1" id="j3">1</td><td class="ctr2" id="k3">1</td></tr><tr><td id="a10"><a href="SPDFApplication.java.html#L55" class="el_method">SPDFApplication(Environment, ApplicationProperties, WebBrowser)</a></td><td class="bar" id="b4"><img src="../jacoco-resources/redbar.gif" width="7" height="10" title="12" alt="12"/></td><td class="ctr2" id="c4">0%</td><td class="bar" id="d7"/><td class="ctr2" id="e7">n/a</td><td class="ctr1" id="f7">1</td><td class="ctr2" id="g7">1</td><td class="ctr1" id="h3">5</td><td class="ctr2" id="i3">5</td><td class="ctr1" id="j4">1</td><td class="ctr2" id="k4">1</td></tr><tr><td id="a8"><a href="SPDFApplication.java.html#L175" class="el_method">setServerPort(String)</a></td><td class="bar" id="b5"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="10" alt="10"/></td><td class="ctr2" id="c5">0%</td><td class="bar" id="d3"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="2" alt="2"/></td><td class="ctr2" id="e3">0%</td><td class="ctr1" id="f3">2</td><td class="ctr2" id="g3">2</td><td class="ctr1" id="h5">4</td><td class="ctr2" id="i5">4</td><td class="ctr1" id="j5">1</td><td class="ctr2" id="k5">1</td></tr><tr><td id="a9"><a href="SPDFApplication.java.html#L185" class="el_method">setServerPortStatic(String)</a></td><td class="bar" id="b6"><img src="../jacoco-resources/redbar.gif" width="6" height="10" title="10" alt="10"/></td><td class="ctr2" id="c6">0%</td><td class="bar" id="d4"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="2" alt="2"/></td><td class="ctr2" id="e4">0%</td><td class="ctr1" id="f4">2</td><td class="ctr2" id="g4">2</td><td class="ctr1" id="h6">4</td><td class="ctr2" id="i6">4</td><td class="ctr1" id="j6">1</td><td class="ctr2" id="k6">1</td></tr><tr><td id="a0"><a href="SPDFApplication.java.html#L196" class="el_method">cleanup()</a></td><td class="bar" id="b7"><img src="../jacoco-resources/redbar.gif" width="4" height="10" title="7" alt="7"/></td><td class="ctr2" id="c7">0%</td><td class="bar" id="d5"><img src="../jacoco-resources/redbar.gif" width="13" height="10" title="2" alt="2"/></td><td class="ctr2" id="e5">0%</td><td class="ctr1" id="f5">2</td><td class="ctr2" id="g5">2</td><td class="ctr1" id="h7">3</td><td class="ctr2" id="i7">3</td><td class="ctr1" id="j7">1</td><td class="ctr2" id="k7">1</td></tr><tr><td id="a11"><a href="SPDFApplication.java.html#L33" class="el_method">static {...}</a></td><td class="bar" id="b8"><img src="../jacoco-resources/redbar.gif" width="2" height="10" title="4" alt="4"/></td><td class="ctr2" id="c8">0%</td><td class="bar" id="d8"/><td class="ctr2" id="e8">n/a</td><td class="ctr1" id="f8">1</td><td class="ctr2" id="g8">1</td><td class="ctr1" id="h8">1</td><td class="ctr2" id="i8">1</td><td class="ctr1" id="j8">1</td><td class="ctr2" id="k8">1</td></tr><tr><td id="a2"><a href="SPDFApplication.java.html#L222" class="el_method">getStaticBaseUrl()</a></td><td class="bar" id="b9"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="c9">0%</td><td class="bar" id="d9"/><td class="ctr2" id="e9">n/a</td><td class="ctr1" id="f9">1</td><td class="ctr2" id="g9">1</td><td class="ctr1" id="h9">1</td><td class="ctr2" id="i9">1</td><td class="ctr1" id="j9">1</td><td class="ctr2" id="k9">1</td></tr><tr><td id="a4"><a href="SPDFApplication.java.html#L226" class="el_method">getStaticPort()</a></td><td class="bar" id="b10"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="c10">0%</td><td class="bar" id="d10"/><td class="ctr2" id="e10">n/a</td><td class="ctr1" id="f10">1</td><td class="ctr2" id="g10">1</td><td class="ctr1" id="h10">1</td><td class="ctr2" id="i10">1</td><td class="ctr1" id="j10">1</td><td class="ctr2" id="k10">1</td></tr><tr><td id="a3"><a href="SPDFApplication.java.html#L230" class="el_method">getStaticContextPath()</a></td><td class="bar" id="b11"><img src="../jacoco-resources/redbar.gif" width="1" height="10" title="2" alt="2"/></td><td class="ctr2" id="c11">0%</td><td class="bar" id="d11"/><td class="ctr2" id="e11">n/a</td><td class="ctr1" id="f11">1</td><td class="ctr2" id="g11">1</td><td class="ctr1" id="h11">1</td><td class="ctr2" id="i11">1</td><td class="ctr1" id="j11">1</td><td class="ctr2" id="k11">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF/SPDFApplication.java.html b/testResult/html/stirling.software.SPDF/SPDFApplication.java.html
    new file mode 100644
    index 000000000..3d5b80047
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF/SPDFApplication.java.html
    @@ -0,0 +1,233 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>SPDFApplication.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <a href="index.source.html" class="el_package">stirling.software.SPDF</a> &gt; <span class="el_source">SPDFApplication.java</span></div><h1>SPDFApplication.java</h1><pre class="source lang-java linenums">package stirling.software.SPDF;
    +
    +import java.io.IOException;
    +import java.net.URISyntaxException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.Map;
    +import java.util.Properties;
    +
    +import org.springframework.beans.factory.annotation.Autowired;
    +import org.springframework.beans.factory.annotation.Value;
    +import org.springframework.boot.SpringApplication;
    +import org.springframework.boot.autoconfigure.SpringBootApplication;
    +import org.springframework.core.env.Environment;
    +import org.springframework.scheduling.annotation.EnableScheduling;
    +
    +import io.github.pixee.security.SystemCommand;
    +
    +import jakarta.annotation.PostConstruct;
    +import jakarta.annotation.PreDestroy;
    +
    +import lombok.extern.slf4j.Slf4j;
    +
    +import stirling.software.SPDF.UI.WebBrowser;
    +import stirling.software.SPDF.config.ConfigInitializer;
    +import stirling.software.SPDF.config.InstallationPathConfig;
    +import stirling.software.SPDF.model.ApplicationProperties;
    +import stirling.software.SPDF.utils.UrlUtils;
    +
    +<span class="nc" id="L33">@Slf4j</span>
    +@EnableScheduling
    +@SpringBootApplication
    +public class SPDFApplication {
    +
    +    private static String serverPortStatic;
    +    private static String baseUrlStatic;
    +    private static String contextPathStatic;
    +
    +    private final Environment env;
    +    private final ApplicationProperties applicationProperties;
    +    private final WebBrowser webBrowser;
    +
    +    @Value(&quot;${baseUrl:http://localhost}&quot;)
    +    private String baseUrl;
    +
    +    @Value(&quot;${server.servlet.context-path:/}&quot;)
    +    private String contextPath;
    +
    +    public SPDFApplication(
    +            Environment env,
    +            ApplicationProperties applicationProperties,
    +<span class="nc" id="L55">            @Autowired(required = false) WebBrowser webBrowser) {</span>
    +<span class="nc" id="L56">        this.env = env;</span>
    +<span class="nc" id="L57">        this.applicationProperties = applicationProperties;</span>
    +<span class="nc" id="L58">        this.webBrowser = webBrowser;</span>
    +<span class="nc" id="L59">    }</span>
    +
    +    public static void main(String[] args) throws IOException, InterruptedException {
    +<span class="nc" id="L62">        SpringApplication app = new SpringApplication(SPDFApplication.class);</span>
    +
    +<span class="nc" id="L64">        Properties props = new Properties();</span>
    +
    +<span class="nc bnc" id="L66" title="All 2 branches missed.">        if (Boolean.parseBoolean(System.getProperty(&quot;STIRLING_PDF_DESKTOP_UI&quot;, &quot;false&quot;))) {</span>
    +<span class="nc" id="L67">            System.setProperty(&quot;java.awt.headless&quot;, &quot;false&quot;);</span>
    +<span class="nc" id="L68">            app.setHeadless(false);</span>
    +<span class="nc" id="L69">            props.put(&quot;java.awt.headless&quot;, &quot;false&quot;);</span>
    +<span class="nc" id="L70">            props.put(&quot;spring.main.web-application-type&quot;, &quot;servlet&quot;);</span>
    +
    +<span class="nc" id="L72">            int desiredPort = 8080;</span>
    +<span class="nc" id="L73">            String port = UrlUtils.findAvailablePort(desiredPort);</span>
    +<span class="nc" id="L74">            props.put(&quot;server.port&quot;, port);</span>
    +<span class="nc" id="L75">            System.setProperty(&quot;server.port&quot;, port);</span>
    +<span class="nc" id="L76">            log.info(&quot;Desktop UI mode: Using port {}&quot;, port);</span>
    +        }
    +
    +<span class="nc" id="L79">        app.setAdditionalProfiles(getActiveProfile(args));</span>
    +
    +<span class="nc" id="L81">        ConfigInitializer initializer = new ConfigInitializer();</span>
    +        try {
    +<span class="nc" id="L83">            initializer.ensureConfigExists();</span>
    +<span class="nc" id="L84">        } catch (IOException | URISyntaxException e) {</span>
    +<span class="nc" id="L85">            log.error(&quot;Error initialising configuration&quot;, e);</span>
    +<span class="nc" id="L86">        }</span>
    +<span class="nc" id="L87">        Map&lt;String, String&gt; propertyFiles = new HashMap&lt;&gt;();</span>
    +
    +        // External config files
    +<span class="nc" id="L90">        Path settingsPath = Paths.get(InstallationPathConfig.getSettingsPath());</span>
    +<span class="nc" id="L91">        log.info(&quot;Settings file: {}&quot;, settingsPath.toString());</span>
    +<span class="nc bnc" id="L92" title="All 2 branches missed.">        if (Files.exists(settingsPath)) {</span>
    +<span class="nc" id="L93">            propertyFiles.put(</span>
    +<span class="nc" id="L94">                    &quot;spring.config.additional-location&quot;, &quot;file:&quot; + settingsPath.toString());</span>
    +        } else {
    +<span class="nc" id="L96">            log.warn(&quot;External configuration file '{}' does not exist.&quot;, settingsPath.toString());</span>
    +        }
    +
    +<span class="nc" id="L99">        Path customSettingsPath = Paths.get(InstallationPathConfig.getCustomSettingsPath());</span>
    +<span class="nc" id="L100">        log.info(&quot;Custom settings file: {}&quot;, customSettingsPath.toString());</span>
    +<span class="nc bnc" id="L101" title="All 2 branches missed.">        if (Files.exists(customSettingsPath)) {</span>
    +<span class="nc" id="L102">            String existingLocation =</span>
    +<span class="nc" id="L103">                    propertyFiles.getOrDefault(&quot;spring.config.additional-location&quot;, &quot;&quot;);</span>
    +<span class="nc bnc" id="L104" title="All 2 branches missed.">            if (!existingLocation.isEmpty()) {</span>
    +<span class="nc" id="L105">                existingLocation += &quot;,&quot;;</span>
    +            }
    +<span class="nc" id="L107">            propertyFiles.put(</span>
    +                    &quot;spring.config.additional-location&quot;,
    +<span class="nc" id="L109">                    existingLocation + &quot;file:&quot; + customSettingsPath.toString());</span>
    +<span class="nc" id="L110">        } else {</span>
    +<span class="nc" id="L111">            log.warn(</span>
    +                    &quot;Custom configuration file '{}' does not exist.&quot;,
    +<span class="nc" id="L113">                    customSettingsPath.toString());</span>
    +        }
    +<span class="nc" id="L115">        Properties finalProps = new Properties();</span>
    +
    +<span class="nc bnc" id="L117" title="All 2 branches missed.">        if (!propertyFiles.isEmpty()) {</span>
    +<span class="nc" id="L118">            finalProps.putAll(</span>
    +<span class="nc" id="L119">                    Collections.singletonMap(</span>
    +                            &quot;spring.config.additional-location&quot;,
    +<span class="nc" id="L121">                            propertyFiles.get(&quot;spring.config.additional-location&quot;)));</span>
    +        }
    +
    +<span class="nc bnc" id="L124" title="All 2 branches missed.">        if (!props.isEmpty()) {</span>
    +<span class="nc" id="L125">            finalProps.putAll(props);</span>
    +        }
    +<span class="nc" id="L127">        app.setDefaultProperties(finalProps);</span>
    +
    +<span class="nc" id="L129">        app.run(args);</span>
    +
    +        // Ensure directories are created
    +        try {
    +<span class="nc" id="L133">            Files.createDirectories(Path.of(InstallationPathConfig.getTemplatesPath()));</span>
    +<span class="nc" id="L134">            Files.createDirectories(Path.of(InstallationPathConfig.getStaticPath()));</span>
    +<span class="nc" id="L135">        } catch (IOException e) {</span>
    +<span class="nc" id="L136">            log.error(&quot;Error creating directories: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L137">        }</span>
    +
    +<span class="nc" id="L139">        printStartupLogs();</span>
    +<span class="nc" id="L140">    }</span>
    +
    +    @PostConstruct
    +    public void init() {
    +<span class="nc" id="L144">        baseUrlStatic = this.baseUrl;</span>
    +<span class="nc" id="L145">        contextPathStatic = this.contextPath;</span>
    +<span class="nc" id="L146">        String url = baseUrl + &quot;:&quot; + getStaticPort() + contextPath;</span>
    +<span class="nc bnc" id="L147" title="All 2 branches missed.">        if (webBrowser != null</span>
    +<span class="nc bnc" id="L148" title="All 2 branches missed.">                &amp;&amp; Boolean.parseBoolean(System.getProperty(&quot;STIRLING_PDF_DESKTOP_UI&quot;, &quot;false&quot;))) {</span>
    +<span class="nc" id="L149">            webBrowser.initWebUI(url);</span>
    +        } else {
    +<span class="nc" id="L151">            String browserOpenEnv = env.getProperty(&quot;BROWSER_OPEN&quot;);</span>
    +<span class="nc bnc" id="L152" title="All 4 branches missed.">            boolean browserOpen = browserOpenEnv != null &amp;&amp; &quot;true&quot;.equalsIgnoreCase(browserOpenEnv);</span>
    +<span class="nc bnc" id="L153" title="All 2 branches missed.">            if (browserOpen) {</span>
    +                try {
    +<span class="nc" id="L155">                    String os = System.getProperty(&quot;os.name&quot;).toLowerCase();</span>
    +<span class="nc" id="L156">                    Runtime rt = Runtime.getRuntime();</span>
    +<span class="nc bnc" id="L157" title="All 2 branches missed.">                    if (os.contains(&quot;win&quot;)) {</span>
    +                        // For Windows
    +<span class="nc" id="L159">                        SystemCommand.runCommand(rt, &quot;rundll32 url.dll,FileProtocolHandler &quot; + url);</span>
    +<span class="nc bnc" id="L160" title="All 2 branches missed.">                    } else if (os.contains(&quot;mac&quot;)) {</span>
    +<span class="nc" id="L161">                        SystemCommand.runCommand(rt, &quot;open &quot; + url);</span>
    +<span class="nc bnc" id="L162" title="All 4 branches missed.">                    } else if (os.contains(&quot;nix&quot;) || os.contains(&quot;nux&quot;)) {</span>
    +<span class="nc" id="L163">                        SystemCommand.runCommand(rt, &quot;xdg-open &quot; + url);</span>
    +                    }
    +<span class="nc" id="L165">                } catch (IOException e) {</span>
    +<span class="nc" id="L166">                    log.error(&quot;Error opening browser: {}&quot;, e.getMessage());</span>
    +<span class="nc" id="L167">                }</span>
    +            }
    +        }
    +<span class="nc" id="L170">        log.info(&quot;Running configs {}&quot;, applicationProperties.toString());</span>
    +<span class="nc" id="L171">    }</span>
    +
    +    @Value(&quot;${server.port:8080}&quot;)
    +    public void setServerPort(String port) {
    +<span class="nc bnc" id="L175" title="All 2 branches missed.">        if (&quot;auto&quot;.equalsIgnoreCase(port)) {</span>
    +            // Use Spring Boot's automatic port assignment (server.port=0)
    +<span class="nc" id="L177">            SPDFApplication.serverPortStatic =</span>
    +                    &quot;0&quot;; // This will let Spring Boot assign an available port
    +        } else {
    +<span class="nc" id="L180">            SPDFApplication.serverPortStatic = port;</span>
    +        }
    +<span class="nc" id="L182">    }</span>
    +
    +    public static void setServerPortStatic(String port) {
    +<span class="nc bnc" id="L185" title="All 2 branches missed.">        if (&quot;auto&quot;.equalsIgnoreCase(port)) {</span>
    +            // Use Spring Boot's automatic port assignment (server.port=0)
    +<span class="nc" id="L187">            SPDFApplication.serverPortStatic =</span>
    +                    &quot;0&quot;; // This will let Spring Boot assign an available port
    +        } else {
    +<span class="nc" id="L190">            SPDFApplication.serverPortStatic = port;</span>
    +        }
    +<span class="nc" id="L192">    }</span>
    +
    +    @PreDestroy
    +    public void cleanup() {
    +<span class="nc bnc" id="L196" title="All 2 branches missed.">        if (webBrowser != null) {</span>
    +<span class="nc" id="L197">            webBrowser.cleanup();</span>
    +        }
    +<span class="nc" id="L199">    }</span>
    +
    +    private static void printStartupLogs() {
    +<span class="nc" id="L202">        log.info(&quot;Stirling-PDF Started.&quot;);</span>
    +<span class="nc" id="L203">        String url = baseUrlStatic + &quot;:&quot; + getStaticPort() + contextPathStatic;</span>
    +<span class="nc" id="L204">        log.info(&quot;Navigate to {}&quot;, url);</span>
    +<span class="nc" id="L205">    }</span>
    +
    +    private static String[] getActiveProfile(String[] args) {
    +<span class="nc bnc" id="L208" title="All 2 branches missed.">        if (args == null) {</span>
    +<span class="nc" id="L209">            return new String[] {&quot;default&quot;};</span>
    +        }
    +
    +<span class="nc bnc" id="L212" title="All 2 branches missed.">        for (String arg : args) {</span>
    +<span class="nc bnc" id="L213" title="All 2 branches missed.">            if (arg.contains(&quot;spring.profiles.active&quot;)) {</span>
    +<span class="nc" id="L214">                return arg.substring(args[0].indexOf('=') + 1).split(&quot;, &quot;);</span>
    +            }
    +        }
    +
    +<span class="nc" id="L218">        return new String[] {&quot;default&quot;};</span>
    +    }
    +
    +    public static String getStaticBaseUrl() {
    +<span class="nc" id="L222">        return baseUrlStatic;</span>
    +    }
    +
    +    public static String getStaticPort() {
    +<span class="nc" id="L226">        return serverPortStatic;</span>
    +    }
    +
    +    public static String getStaticContextPath() {
    +<span class="nc" id="L230">        return contextPathStatic;</span>
    +    }
    +}
    +</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF/index.html b/testResult/html/stirling.software.SPDF/index.html
    new file mode 100644
    index 000000000..bcf2d1efe
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF/index.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.source.html" class="el_source">Source Files</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF</span></div><h1>stirling.software.SPDF</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">525 of 525</td><td class="ctr2">0%</td><td class="bar">56 of 56</td><td class="ctr2">0%</td><td class="ctr1">46</td><td class="ctr2">46</td><td class="ctr1">147</td><td class="ctr2">147</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="SPDFApplication.html" class="el_class">SPDFApplication</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="402" alt="402"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="42" alt="42"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">33</td><td class="ctr2" id="g0">33</td><td class="ctr1" id="h0">105</td><td class="ctr2" id="i0">105</td><td class="ctr1" id="j0">12</td><td class="ctr2" id="k0">12</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="LibreOfficeListener.html" class="el_class">LibreOfficeListener</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="123" alt="123"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="14" alt="14"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">13</td><td class="ctr2" id="g1">13</td><td class="ctr1" id="h1">42</td><td class="ctr2" id="i1">42</td><td class="ctr1" id="j1">6</td><td class="ctr2" id="k1">6</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file
    diff --git a/testResult/html/stirling.software.SPDF/index.source.html b/testResult/html/stirling.software.SPDF/index.source.html
    new file mode 100644
    index 000000000..395e17ec5
    --- /dev/null
    +++ b/testResult/html/stirling.software.SPDF/index.source.html
    @@ -0,0 +1 @@
    +<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="zh"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>stirling.software.SPDF</title><script type="text/javascript" src="../jacoco-resources/sort.js"></script></head><body onload="initialSort(['breadcrumb', 'coveragetable'])"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="index.html" class="el_class">Classes</a><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Stirling-PDF</a> &gt; <span class="el_package">stirling.software.SPDF</span></div><h1>stirling.software.SPDF</h1><table class="coverage" cellspacing="0" id="coveragetable"><thead><tr><td class="sortable" id="a" onclick="toggleSort(this)">Element</td><td class="down sortable bar" id="b" onclick="toggleSort(this)">Missed Instructions</td><td class="sortable ctr2" id="c" onclick="toggleSort(this)">Cov.</td><td class="sortable bar" id="d" onclick="toggleSort(this)">Missed Branches</td><td class="sortable ctr2" id="e" onclick="toggleSort(this)">Cov.</td><td class="sortable ctr1" id="f" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="g" onclick="toggleSort(this)">Cxty</td><td class="sortable ctr1" id="h" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="i" onclick="toggleSort(this)">Lines</td><td class="sortable ctr1" id="j" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="k" onclick="toggleSort(this)">Methods</td><td class="sortable ctr1" id="l" onclick="toggleSort(this)">Missed</td><td class="sortable ctr2" id="m" onclick="toggleSort(this)">Classes</td></tr></thead><tfoot><tr><td>Total</td><td class="bar">525 of 525</td><td class="ctr2">0%</td><td class="bar">56 of 56</td><td class="ctr2">0%</td><td class="ctr1">46</td><td class="ctr2">46</td><td class="ctr1">147</td><td class="ctr2">147</td><td class="ctr1">18</td><td class="ctr2">18</td><td class="ctr1">2</td><td class="ctr2">2</td></tr></tfoot><tbody><tr><td id="a1"><a href="SPDFApplication.java.html" class="el_source">SPDFApplication.java</a></td><td class="bar" id="b0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="402" alt="402"/></td><td class="ctr2" id="c0">0%</td><td class="bar" id="d0"><img src="../jacoco-resources/redbar.gif" width="120" height="10" title="42" alt="42"/></td><td class="ctr2" id="e0">0%</td><td class="ctr1" id="f0">33</td><td class="ctr2" id="g0">33</td><td class="ctr1" id="h0">105</td><td class="ctr2" id="i0">105</td><td class="ctr1" id="j0">12</td><td class="ctr2" id="k0">12</td><td class="ctr1" id="l0">1</td><td class="ctr2" id="m0">1</td></tr><tr><td id="a0"><a href="LibreOfficeListener.java.html" class="el_source">LibreOfficeListener.java</a></td><td class="bar" id="b1"><img src="../jacoco-resources/redbar.gif" width="36" height="10" title="123" alt="123"/></td><td class="ctr2" id="c1">0%</td><td class="bar" id="d1"><img src="../jacoco-resources/redbar.gif" width="40" height="10" title="14" alt="14"/></td><td class="ctr2" id="e1">0%</td><td class="ctr1" id="f1">13</td><td class="ctr2" id="g1">13</td><td class="ctr1" id="h1">42</td><td class="ctr2" id="i1">42</td><td class="ctr1" id="j1">6</td><td class="ctr2" id="k1">6</td><td class="ctr1" id="l1">1</td><td class="ctr2" id="m1">1</td></tr></tbody></table><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.12.202403310830</span></div></body></html>
    \ No newline at end of file