mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
feat: Add RegexPatternUtils for centralized regex management, file naming funcs, UtilityClass annotation (#4218)
Co-authored-by: Copilot <[email protected]> Co-authored-by: Anthony Stirling <[email protected]>
This commit is contained in:
co-authored by
Copilot
Anthony Stirling
parent
133e6d3de6
commit
045f4cc591
+5
-1
@@ -14,6 +14,7 @@ import stirling.software.common.model.ApplicationProperties.Driver;
|
||||
import stirling.software.common.model.ApplicationProperties.Premium;
|
||||
import stirling.software.common.model.ApplicationProperties.Security;
|
||||
import stirling.software.common.model.exception.UnsupportedProviderException;
|
||||
import stirling.software.common.util.RegexPatternUtils;
|
||||
|
||||
class ApplicationPropertiesLogicTest {
|
||||
|
||||
@@ -38,7 +39,10 @@ class ApplicationPropertiesLogicTest {
|
||||
new ApplicationProperties.TempFileManagement();
|
||||
|
||||
String expectedBase =
|
||||
java.lang.System.getProperty("java.io.tmpdir").replaceAll("/+$", "")
|
||||
RegexPatternUtils.getInstance()
|
||||
.getTrailingSlashesPattern()
|
||||
.matcher(java.lang.System.getProperty("java.io.tmpdir"))
|
||||
.replaceAll("")
|
||||
+ "/stirling-pdf";
|
||||
assertEquals(expectedBase, normalize.apply(tfm.getBaseTmpDir()));
|
||||
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -154,4 +161,223 @@ public class GeneralUtilsTest {
|
||||
List<Integer> result = GeneralUtils.parsePageList(new String[] {"1,3,7-8"}, 8, false);
|
||||
assertEquals(List.of(0, 2, 6, 7), result, "Range should be parsed correctly.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveExtension() {
|
||||
// Test common cases (should use fast string operations)
|
||||
assertEquals("document", GeneralUtils.removeExtension("document.pdf"));
|
||||
assertEquals("image", GeneralUtils.removeExtension("image.jpg"));
|
||||
assertEquals("file.backup", GeneralUtils.removeExtension("file.backup.zip"));
|
||||
assertEquals("complex.file.name", GeneralUtils.removeExtension("complex.file.name.txt"));
|
||||
|
||||
// Test edge cases (should fall back to regex)
|
||||
assertEquals("default", GeneralUtils.removeExtension(null));
|
||||
assertEquals("noextension", GeneralUtils.removeExtension("noextension"));
|
||||
assertEquals(
|
||||
".hidden", GeneralUtils.removeExtension(".hidden")); // Hidden file, no extension
|
||||
assertEquals("trailing.", GeneralUtils.removeExtension("trailing.")); // Trailing dot
|
||||
assertEquals("", GeneralUtils.removeExtension(""));
|
||||
assertEquals("a", GeneralUtils.removeExtension("a"));
|
||||
|
||||
// Test multiple dots
|
||||
assertEquals("file.with.multiple", GeneralUtils.removeExtension("file.with.multiple.dots"));
|
||||
assertEquals("path/to/file", GeneralUtils.removeExtension("path/to/file.ext"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAppendSuffix() {
|
||||
// Normal cases
|
||||
assertEquals("document_processed", GeneralUtils.appendSuffix("document", "_processed"));
|
||||
assertEquals("file.txt", GeneralUtils.appendSuffix("file", ".txt"));
|
||||
|
||||
// Null handling
|
||||
assertEquals("default_suffix", GeneralUtils.appendSuffix(null, "_suffix"));
|
||||
assertEquals("basename", GeneralUtils.appendSuffix("basename", null));
|
||||
assertEquals("default", GeneralUtils.appendSuffix(null, null));
|
||||
|
||||
// Empty strings
|
||||
assertEquals("_suffix", GeneralUtils.appendSuffix("", "_suffix"));
|
||||
assertEquals("basename", GeneralUtils.appendSuffix("basename", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessFilenames() {
|
||||
List<String> filenames = new ArrayList<>();
|
||||
filenames.add("document.pdf");
|
||||
filenames.add("image.jpg");
|
||||
filenames.add("spreadsheet.xlsx");
|
||||
filenames.add("presentation.pptx");
|
||||
filenames.add(null); // Should handle null gracefully
|
||||
filenames.add("noextension");
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
GeneralUtils.processFilenames(filenames, "_processed", results::add);
|
||||
|
||||
List<String> expected =
|
||||
List.of(
|
||||
"document_processed",
|
||||
"image_processed",
|
||||
"spreadsheet_processed",
|
||||
"presentation_processed",
|
||||
"default_processed",
|
||||
"noextension_processed");
|
||||
|
||||
assertEquals(expected, results);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testProcessFilenamesNullHandling() {
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
// Null filenames list
|
||||
GeneralUtils.processFilenames(null, "_suffix", results::add);
|
||||
assertTrue(results.isEmpty(), "Should handle null filenames list");
|
||||
|
||||
// Null processor
|
||||
List<String> filenames = List.of("test.txt");
|
||||
GeneralUtils.processFilenames(filenames, "_suffix", null); // Should not throw
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveExtensionThreadSafety() throws InterruptedException {
|
||||
final int threadCount = 50;
|
||||
final int operationsPerThread = 100;
|
||||
final String[] testFilenames = {
|
||||
"document.pdf", "image.jpg", "data.csv", "presentation.pptx",
|
||||
"archive.zip", "music.mp3", "video.mp4", "text.txt"
|
||||
};
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
|
||||
CountDownLatch latch = new CountDownLatch(threadCount);
|
||||
AtomicInteger successCount = new AtomicInteger(0);
|
||||
List<Exception> exceptions = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
executor.submit(
|
||||
() -> {
|
||||
try {
|
||||
for (int j = 0; j < operationsPerThread; j++) {
|
||||
String filename = testFilenames[j % testFilenames.length];
|
||||
String result = GeneralUtils.removeExtension(filename);
|
||||
|
||||
// Verify result is correct
|
||||
assertFalse(
|
||||
result.contains("."),
|
||||
"Result should not contain extension: " + result);
|
||||
assertTrue(
|
||||
filename.startsWith(result),
|
||||
"Original should start with result: "
|
||||
+ filename
|
||||
+ " -> "
|
||||
+ result);
|
||||
}
|
||||
successCount.incrementAndGet();
|
||||
} catch (Exception e) {
|
||||
exceptions.add(e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
assertTrue(latch.await(10, TimeUnit.SECONDS), "All threads should complete");
|
||||
|
||||
if (!exceptions.isEmpty()) {
|
||||
fail("Thread safety test failed with exceptions: " + exceptions);
|
||||
}
|
||||
|
||||
assertEquals(threadCount, successCount.get(), "All threads should succeed");
|
||||
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBatchProcessingPerformance() {
|
||||
List<String> filenames = new ArrayList<>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
filenames.add("file" + i + ".pdf");
|
||||
filenames.add("document" + i + ".docx");
|
||||
filenames.add("image" + i + ".jpg");
|
||||
}
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
GeneralUtils.processFilenames(filenames, "_processed", results::add);
|
||||
|
||||
assertEquals(filenames.size(), results.size(), "Should process all filenames");
|
||||
|
||||
assertTrue(results.contains("file0_processed"), "Should contain processed filename");
|
||||
assertTrue(results.contains("document500_processed"), "Should contain processed filename");
|
||||
assertTrue(results.contains("image999_processed"), "Should contain processed filename");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHybridStringRegexApproach() {
|
||||
|
||||
String[] edgeCases = {
|
||||
"", // Empty string
|
||||
".", // Just a dot
|
||||
"..", // Two dots
|
||||
"...", // Three dots
|
||||
".hidden", // Hidden file
|
||||
"file.", // Trailing dot
|
||||
"a.b.c.d.e.f.g", // Many extensions
|
||||
"no-extension-here", // No extension
|
||||
"file..double.dot" // Double dots
|
||||
};
|
||||
|
||||
for (String edgeCase : edgeCases) {
|
||||
String result = GeneralUtils.removeExtension(edgeCase);
|
||||
assertNotNull(result, "Result should not be null for: " + edgeCase);
|
||||
|
||||
// For specific edge cases, verify expected behavior
|
||||
switch (edgeCase) {
|
||||
case "" -> assertEquals("", result, "Empty string should remain empty");
|
||||
case "." -> assertEquals(".", result, "Single dot should remain unchanged");
|
||||
case ".." -> assertEquals("..", result, "Double dots should remain unchanged");
|
||||
case "..." -> assertEquals("...", result, "Triple dots should remain unchanged");
|
||||
case ".hidden" ->
|
||||
assertEquals(".hidden", result, "Hidden file should remain unchanged");
|
||||
case "file." ->
|
||||
assertEquals("file.", result, "Trailing dot should remain unchanged");
|
||||
case "no-extension-here" ->
|
||||
assertEquals(
|
||||
"no-extension-here",
|
||||
result,
|
||||
"No extension should remain unchanged");
|
||||
case "a.b.c.d.e.f.g" ->
|
||||
assertEquals(
|
||||
"a.b.c.d.e.f",
|
||||
result,
|
||||
"Multiple extensions should remove last one");
|
||||
case "file..double.dot" ->
|
||||
assertEquals(
|
||||
"file..double",
|
||||
result,
|
||||
"Double dot case should remove last extension");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetTitleFromFilename() {
|
||||
// Test normal cases
|
||||
assertEquals("document", GeneralUtils.getTitleFromFilename("document.pdf"));
|
||||
assertEquals("presentation", GeneralUtils.getTitleFromFilename("presentation.pptx"));
|
||||
assertEquals("file.backup", GeneralUtils.getTitleFromFilename("file.backup.zip"));
|
||||
|
||||
// Test null and empty handling
|
||||
assertEquals("Untitled", GeneralUtils.getTitleFromFilename(null));
|
||||
assertEquals("Untitled", GeneralUtils.getTitleFromFilename(""));
|
||||
|
||||
// Test edge cases
|
||||
assertEquals(".hidden", GeneralUtils.getTitleFromFilename(".hidden"));
|
||||
assertEquals("file.", GeneralUtils.getTitleFromFilename("file."));
|
||||
assertEquals("noextension", GeneralUtils.getTitleFromFilename("noextension"));
|
||||
|
||||
// Test complex cases
|
||||
assertEquals(
|
||||
"complex.file.name", GeneralUtils.getTitleFromFilename("complex.file.name.txt"));
|
||||
assertEquals("path/to/file", GeneralUtils.getTitleFromFilename("path/to/file.ext"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,23 +65,22 @@ public class PdfUtilsTest {
|
||||
doc1.addPage(new PDPage());
|
||||
doc1.addPage(new PDPage());
|
||||
doc1.addPage(new PDPage());
|
||||
PdfUtils utils = new PdfUtils();
|
||||
assertTrue(utils.pageCount(doc1, 2, "greater"));
|
||||
assertTrue(PdfUtils.pageCount(doc1, 2, "greater"));
|
||||
|
||||
PDDocument doc2 = new PDDocument();
|
||||
doc2.addPage(new PDPage());
|
||||
doc2.addPage(new PDPage());
|
||||
doc2.addPage(new PDPage());
|
||||
assertTrue(utils.pageCount(doc2, 3, "equal"));
|
||||
assertTrue(PdfUtils.pageCount(doc2, 3, "equal"));
|
||||
|
||||
PDDocument doc3 = new PDDocument();
|
||||
doc3.addPage(new PDPage());
|
||||
doc3.addPage(new PDPage());
|
||||
assertTrue(utils.pageCount(doc3, 5, "less"));
|
||||
assertTrue(PdfUtils.pageCount(doc3, 5, "less"));
|
||||
|
||||
PDDocument doc4 = new PDDocument();
|
||||
doc4.addPage(new PDPage());
|
||||
assertThrows(IllegalArgumentException.class, () -> utils.pageCount(doc4, 1, "bad"));
|
||||
assertThrows(IllegalArgumentException.class, () -> PdfUtils.pageCount(doc4, 1, "bad"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -91,8 +90,7 @@ public class PdfUtilsTest {
|
||||
doc.addPage(page);
|
||||
PDRectangle rect = page.getMediaBox();
|
||||
String expected = rect.getWidth() + "x" + rect.getHeight();
|
||||
PdfUtils utils = new PdfUtils();
|
||||
assertTrue(utils.pageSize(doc, expected));
|
||||
assertTrue(PdfUtils.pageSize(doc, expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package stirling.software.common.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class RegexPatternUtilsTest {
|
||||
|
||||
private RegexPatternUtils utils;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
utils = RegexPatternUtils.getInstance();
|
||||
utils.clearCache(); // Start with clean cache for each test
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPatternCaching() {
|
||||
String regex = "test\\d+";
|
||||
|
||||
Pattern pattern1 = utils.getPattern(regex);
|
||||
assertNotNull(pattern1);
|
||||
assertTrue(utils.isCached(regex));
|
||||
assertEquals(
|
||||
1, utils.getCacheSize()); // Should have at least 1 pattern (plus precompiled ones
|
||||
// are cleared)
|
||||
|
||||
Pattern pattern2 = utils.getPattern(regex);
|
||||
assertSame(pattern1, pattern2); // Should be the exact same object
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPatternWithFlags() {
|
||||
String regex = "test";
|
||||
int flags = Pattern.CASE_INSENSITIVE;
|
||||
|
||||
Pattern pattern1 = utils.getPattern(regex, flags);
|
||||
Pattern pattern2 = utils.getPattern(regex); // No flags
|
||||
|
||||
assertNotSame(pattern1, pattern2); // Different flags = different cached patterns
|
||||
assertTrue(utils.isCached(regex, flags));
|
||||
assertTrue(utils.isCached(regex, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCacheEviction() {
|
||||
String regex = "evict\\d+";
|
||||
|
||||
utils.getPattern(regex);
|
||||
assertTrue(utils.isCached(regex));
|
||||
|
||||
boolean removed = utils.removeFromCache(regex);
|
||||
assertTrue(removed);
|
||||
assertFalse(utils.isCached(regex));
|
||||
|
||||
boolean removedAgain = utils.removeFromCache(regex);
|
||||
assertFalse(removedAgain);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullRegexHandling() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> {
|
||||
utils.getPattern(null);
|
||||
});
|
||||
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> {
|
||||
utils.getPattern(null, Pattern.CASE_INSENSITIVE);
|
||||
});
|
||||
|
||||
assertFalse(utils.isCached(null));
|
||||
assertFalse(utils.removeFromCache(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCommonPatterns() {
|
||||
Pattern whitespace = utils.getWhitespacePattern();
|
||||
assertTrue(whitespace.matcher(" \t ").matches());
|
||||
|
||||
Pattern trailing = utils.getTrailingSlashesPattern();
|
||||
assertTrue(trailing.matcher("/path/to/dir///").find());
|
||||
|
||||
Pattern filename = utils.getSafeFilenamePattern();
|
||||
assertTrue(filename.matcher("bad<file>name").find());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCreateSearchPattern() {
|
||||
String regex = "Hello";
|
||||
|
||||
Pattern caseSensitive = utils.createSearchPattern(regex, false);
|
||||
Pattern caseInsensitive = utils.createSearchPattern(regex, true);
|
||||
|
||||
assertTrue(caseSensitive.matcher("Hello").matches());
|
||||
assertFalse(caseSensitive.matcher("hello").matches());
|
||||
|
||||
assertTrue(caseInsensitive.matcher("Hello").matches());
|
||||
assertTrue(caseInsensitive.matcher("hello").matches());
|
||||
assertTrue(caseInsensitive.matcher("HELLO").matches());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSingletonBehavior() {
|
||||
RegexPatternUtils instance1 = RegexPatternUtils.getInstance();
|
||||
RegexPatternUtils instance2 = RegexPatternUtils.getInstance();
|
||||
|
||||
assertSame(instance1, instance2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user