Porovnání složek java – průvodce používáním GroupDocs.Comparison
Už jste strávili hodiny ručním kontrolováním, které soubory se změnily mezi dvěma verzemi projektu? Nejste v tom sami. GroupDocs.Comparison for Java tuto nudnou úlohu usnadňuje tím, že umožňuje porovnat dvě složky jedním voláním API. V tomto tutoriálu se naučíte, jak compare folders java efektivně, od počátečního nastavení až po pokročilé ladění výkonu pro obrovské kódové základny.
GroupDocs.Comparison for Java je knihovna, která umožňuje programové porovnání dokumentů a adresářů. Podporuje více než 70 vstupních a výstupních formátů a dokáže zpracovat adresáře až s 10 000 soubory, aniž by načítala celý souborový soubor do paměti, což z ní činí robustní volbu pro audity v podnikovém měřítku.
Rychlé odpovědi
- Jaká je hlavní knihovna?
groupdocs comparison java - Podporovaná verze Javy? Java 8 nebo vyšší
- Typický čas nastavení? 10–15 minut pro základní porovnání
- Požadavek na licenci? Ano – je potřeba zkušební nebo komerční licence
- Výstupní formáty? HTML (výchozí) nebo PDF
Co je compare folders java?
Fráze „compare folders java“ odkazuje na používání API založeného na Javě k detekci rozdílů – přidaných, odebraných nebo upravených souborů – mezi dvěma stromovými strukturami adresářů. GroupDocs.Comparison poskytuje vysoce úrovňový, nezávislý na souborovém systému způsob, jak tuto operaci provést, a vrací podrobnou HTML nebo PDF zprávu, která zvýrazní každou změnu.
Proč je compare folders java důležité (více, než si myslíte)
Porovnání adresářů není jen o hledání chybějících souborů; je to kritický kontrolní bod pro integritu dat, regulatorní soulad a stabilitu vydání. Automatizací procesu odstraníte lidské chyby, zrychlíte audity a získáte jediný zdroj pravdy, který lze archivovat pro budoucí reference.
Kvantifikované výhody
- Rychlost: Zpracovává adresáře s 5 000 soubory za méně než 30 sekund na typickém 8‑jádrovém serveru.
- Pokrytí: Detekuje změny napříč více než 70 typy dokumentů, od DOCX po PNG.
- Škálovatelnost: Zvládá soubory až do 2 GB každý, aniž by vyčerpala haldu JVM při nastavení režimu streamování.
- Přesnost: Hlásí rozdíly s 99,9 % věrností, zachovává rozvržení, tabulky a obrázky.
Požadavky a nastavení
Než začneme kódovat, ujistěte se, že je vaše prostředí připravené. Zde je, co budete potřebovat (a proč):
Základní požadavky
- Java 8 nebo vyšší – GroupDocs.Comparison používá moderní jazykové funkce a API.
- Maven 3.6+ – Pro spolehlivé řešení závislostí; ruční manipulace s JAR soubory je náchylná k chybám.
- IDE s dobrým podporou Javy – IntelliJ IDEA nebo Eclipse jsou doporučeny pro ladění a refaktoring.
- Alespoň 2 GB RAM – Porovnání velkých adresářů může spotřebovat značnou paměť, zejména při generování HTML zpráv.
Předpoklady znalostí
- Základní syntaxe Javy (cykly, zpracování výjimek, try‑with‑resources).
- Znalost práce se soubory (
java.nio.file.Path,FilesAPI). - Porozumění sekcím
<dependency>a<repository>v Maven.
Volitelné, ale užitečné
- Zkušenost se SLF4J/Logback pro logování.
- Znalost konceptů vícevláknového programování, pokud plánujete paralelizovat porovnání.
- Základní znalost HTML pro úpravu generované zprávy.
Nastavení GroupDocs.Comparison pro Java
Pojďme tuto knihovnu správně integrovat do vašeho projektu. Nastavení je jednoduché, ale je zde několik úskalí, na která je třeba dát pozor.
Maven konfigurace
Přidejte následující závislost a repozitář do vašeho pom.xml. Nezapomeňte nahradit zástupný znak verze nejnovějším číslem vydání z oficiální stránky GroupDocs.
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-comparison</artifactId>
<version>25.2</version>
</dependency>
<repository>
<id>groupdocs-repo</id>
<url>https://repo.groupdocs.com/maven2</url>
</repository>
Tip: Vždy ověřte číslo verze na stránce ke stažení produktu; novější vydání obsahují opravy výkonu a podporu dalších formátů.
Nastavení licence (nepřeskakujte to)
GroupDocs není zdarma, ale nabízí několik licenčních možností:
- Bezplatná zkušební verze: 30‑denní zkušební verze s plnou sadou funkcí — ideální pro hodnocení.
- Dočasná licence: Rozšířená zkušební verze pro vývojová a testovací prostředí.
- Komerční licence: Vyžadována pro nasazení do produkce.
Získejte licenci zde:
- Zakoupit licenci for production
- Získat dočasnou licenci for extended testing
Základní inicializace a testování
Jakmile váš Maven build uspěje, vytvořte jednoduchou testovací třídu, která načte licenci a spustí minimální porovnání. Pokud program začne bez vyhození výjimky, je vaše prostředí správně nakonfigurováno.
import com.groupdocs.comparison.Comparison;
import com.groupdocs.comparison.License;
public class InitTest {
public static void main(String[] args) throws Exception {
License license = new License();
license.setLicense("GroupDocs.Comparison.lic");
// Simple sanity check
Comparison comparison = new Comparison();
System.out.println("GroupDocs.Comparison initialized successfully.");
}
}
Pokud to běží bez chyb, můžete pokračovat. Pokud ne, zkontrolujte nastavení Maven a ujistěte se, že váš počítač může dosáhnout licenčního serveru GroupDocs.
Hlavní implementace: porovnání adresářů
Nyní hlavní událost — skutečné porovnání adresářů. Začneme základní implementací a poté přidáme pokročilé funkce.
Jak porovnat složky java?
Načtěte dvě cesty k adresářům, nakonfigurujte možnosti porovnání a zavolejte API. Pouze ve třech řádcích můžete vygenerovat kompletní HTML diff zprávu, která vypíše každý přidaný, smazaný nebo upravený soubor.
Comparison comparison = new Comparison();
comparison.compare("C:/Project/v1", "C:/Project/v2", "C:/Reports/diff.html");
Metoda compare prohledá obě složky rekurzivně, spáruje soubory podle názvu a zapíše vizuální HTML zprávu do cílového umístění. Zpráva zvýrazňuje změny řádek po řádku pro textové soubory a zobrazuje náhledy vedle sebe pro obrázky a PDF.
Třída Comparison je hlavním vstupním bodem API, který provádí porovnání adresářů a generuje zprávu.
Zabalte volání do bloku try‑with‑resources (nebo použijte metodu close objektu Comparison), aby byly všechny souborové handly rychle uvolněny, zejména při zpracování tisíců souborů.
Pokročilé konfigurační možnosti
Základní nastavení funguje pro většinu scénářů, ale reálné projekty často vyžadují jemně vyladěné chování.
Přizpůsobení výstupních formátů
GroupDocs.Comparison může exportovat zprávy jako PDF, DOCX nebo prostý HTML. Přepínání formátů je tak jednoduché, jako změnit příponu souboru ve volání compare.
Filtrování souborů a adresářů
Pokud vás zajímají jen konkrétní typy souborů (např. .java a .xml), poskytněte filtr predikátu, který přeskočí irelevantní soubory a dramaticky zlepší výkon.
comparison.setFileFilter(path -> path.toString().endsWith(".java") || path.toString().endsWith(".xml"));
Běžné problémy a řešení
Pojďme se zabývat problémy, se kterými pravděpodobně narazíte (protože Murphyho zákon platí i pro kódování).
Problém 1: OutOfMemoryError při velkých adresářích
Přímá odpověď: Zvyšte velikost haldy JVM (-Xmx4g nebo vyšší) a povolte režim streamování v možnostech Comparison, aby se soubory zpracovávaly sekvenčně místo načítání všech do paměti.
Když pracujete s adresáři obsahujícími desítky tisíc souborů, může výchozí přístup v paměti překročit haldu. Režim streamování čte každý soubor na vyžádání, udržuje paměťovou stopu pod 200 MB i při bězích s 10 000 soubory.
Problém 2: FileNotFoundException i přes správné cesty
Přímá odpověď: Ověřte, že Java proces má oprávnění ke čtení zdrojových adresářů a oprávnění k zápisu do výstupní složky; také zajistěte, aby byly mezery nebo speciální znaky v cestě řádně escapovány.
Běžné příčiny zahrnují omezení ACL na úrovni OS, síťové sdílení vyžadující autentizaci a Unicode znaky, které potřebují explicitní zpracování pomocí java.nio.file.Paths.
Problém 3: Porovnání trvá věčnost
Přímá odpověď: Použijte souborové filtry k vyloučení velkých binárních aktiv, povolte vícevláknové zpracování pro nezávislé podadresáře a monitorujte průběh pomocí callback listeneru k včasnému odhalení úzkých míst.
Paralelizace porovnání podadresářů může zkrátit dobu běhu až o 70 % na 8‑jádrovém serveru, zatímco callbacky průběhu vám umožní zobrazit jednoduchý konzolový ukazatel postupu pro dlouho běžící úlohy.
Optimalizace výkonu pro rozsáhlá porovnání
Když pracujete s adresáři obsahujícími tisíce souborů, výkon se stává kritickým. Zde je návod, jak optimalizovat:
Nejlepší praktiky správy paměti
Třída ComparisonOptions vám umožňuje konfigurovat chování procesu porovnání, jako je povolení režimu streamování, nastavení limitů velikosti souborů a výběr výstupních formátů.
- Použijte režim streamování (
ComparisonOptions.setUseStreaming(true)). - Omezte maximální velikost zpracovávaných souborů (
setMaxFileSize(200 * 1024 * 1024)) pro 200 MB. - Explicitně uzavřete objekt
Comparisonpo každém spuštění.
Strategie dávkového zpracování
Rozdělte masivní strom adresářů do logických dávek (např. podle modulu nebo časového rozmezí) a spusťte každou dávku sekvenčně. To zabrání JVM držet v paměti více než jednu dávku najednou.
Paralelní zpracování pro nezávislé adresáře
Pokud máte více párů adresářů k porovnání (např. noční sestavení pro několik mikro‑služeb), spusťte samostatné instance Comparison ve vláknovém poolu. Každé vlákno pracuje na svém páru a využívá všechny CPU jádra.
Reálné případy použití a průmyslové aplikace
Porovnání adresářů není jen nástroj pro vývojáře — používá se napříč odvětvími pro obchodně kritické procesy:
Vývoj softwaru a DevOps
Řízení vydání: Porovnejte složky staging a production před nasazením, abyste zachytili odchylky v konfiguraci. HTML zpráva může být připojena k pull‑requestu pro revizi zainteresovaných stran.
Finance a soulad
Údržba auditního záznamu: Finanční instituce používají porovnání adresářů ke sledování změn dokumentů pro regulatorní soulad, zajišťují, že každá úprava je zaznamenána a archivována.
Správa dat a ETL procesy
Ověření integrity dat: Po hromadné migraci dat spusťte porovnání složek, aby bylo zajištěno, že každý zdrojový soubor byl správně umístěn v cílovém datovém jezeře.
Správa obsahu a publikování
Kontrola verzí pro netechnické týmy: Marketingové týmy mohou porovnat dvě verze složky s assety webu bez nutnosti znalosti Gitu, získají jasný vizuální diff.
Pokročilé tipy a nejlepší praktiky
Po práci s porovnáním adresářů v produkčních prostředích, zde jsou některé těžce naučené lekce:
Logování a monitorování
Integrujte SLF4J s rotujícím souborovým appenderem, aby zachytil čas startu, čas konce, počet zpracovaných souborů a případné výjimky. Tento log se stane neocenitelným při vyšetřování přerušovaných selhání.
Obnova po chybě a odolnost
Zabalte volání compare do retry bloku, který zachytí přechodné I/O chyby (např. výpadky sítě na připojených discích) a znovu spustí porovnání až třikrát před ukončením.
Správa konfigurace
Externalizujte všechny cesty, výstupní formáty a výkonnostní příznaky do souboru application.yml nebo properties. To umožní operačnímu týmu upravit nastavení bez překladu JAR.
Platformově nezávislé zacházení s cestami
Vždy vytvářejte cesty pomocí java.nio.file.Paths.get(...) a při spojování řetězců používejte File.separator. To zabraňuje chybám při přechodu z Windows (\) na Linux (/) prostředí.
Ignorování časových razítek, když nejsou podstatná
Pokud jsou podstatné jen změny obsahu, nastavte CompareOptions.setIgnoreMetadata(true). To zabraňuje falešným pozitivům způsobeným automatickými aktualizacemi časových razítek u kopírovaných souborů.
Řešení běžných problémů při nasazení
Funguje ve vývoji, selže v produkci
Přímá odpověď: Zkontrolujte rozdíly v citlivosti na velikost písmen (Windows vs Linux), ověřte oprávnění souborového systému a nahraďte pevně zakódované oddělovače cest File.separator.
Produkční servery často běží na Linuxu, kde myFile.txt a MyFile.txt jsou odlišné. Použijte API Path k normalizaci velikosti písmen a vyhněte se neúmyslným nesouladům.
Nekonzistentní výsledky
Přímá odpověď: Zajistěte, aby žádný externí proces během běhu porovnání nemodifikoval soubory, a nakonfigurujte CompareOptions tak, aby ignorovaly časová razítka, pokud způsobují falešné rozdíly.
Spuštění porovnání v read‑only snapshotu (např. snapshot připojeného svazku) zaručuje deterministické výsledky.
Často kladené otázky
Q: Jak zvládnout adresáře s miliony souborů?
A: Kombinujte dávkové zpracování, zvyšte haldu JVM (-Xmx8g nebo vyšší), povolte režim streamování a spusťte porovnání podadresářů paralelně. Sekce Strategie dávkového zpracování a Paralelní zpracování poskytují připravené vzory.
Q: Mohu porovnat adresáře umístěné na různých serverech?
A: Ano, ale latence sítě dominuje době běhu. Pro nejlepší výkon nejprve zkopírujte vzdálený adresář lokálně nebo připojte vzdálené sdílení s dostatečnou I/O šířkou pásma před voláním porovnání.
Q: Jaké souborové formáty podporuje GroupDocs.Comparison?
A: GroupDocs.Comparison podporuje více než 70 formátů, včetně DOC/DOCX, PDF, PPT/PPTX, XLS/XLSX, TXT, HTML, XML, CSV a běžných typů obrázků (PNG, JPEG, BMP). Pro nejnovější seznam viz oficiální dokumentace.
Q: Jak mohu integrovat toto porovnání do CI/CD pipeline?
A: Zabalte logiku porovnání do spustitelného JAR nebo Maven pluginu, poté ji vyvolejte jako krok sestavení v Jenkins, GitHub Actions, Azure Pipelines nebo GitLab CI. Exportujte HTML zprávu jako artefakt sestavení pro následnou revizi.
Q: Je možné přizpůsobit vzhled HTML zprávy?
A: Vestavěná HTML šablona je pevná, ale můžete po‑zpracovat vygenerovaný soubor — vložit vlastní CSS nebo JavaScript — aby odpovídal firemnímu brandingu nebo přidal interaktivní prvky.
Poslední aktualizace: 2026-08-09
Testováno s: GroupDocs.Comparison 25.2 (Java)
Autor: GroupDocs
<repositories>
<repository>
<id>repository.groupdocs.com</id>
<name>GroupDocs Repository</name>
<url>https://releases.groupdocs.com/comparison/java/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-comparison</artifactId>
<version>25.2</version>
</dependency>
</dependencies>
import com.groupdocs.comparison.Comparer;
public class Main {
public static void main(String[] args) {
try {
Comparer comparer = new Comparer();
System.out.println("GroupDocs.Comparison initialized successfully!");
} catch (Exception e) {
System.err.println("Setup issue: " + e.getMessage());
}
}
}
String sourceDirectoryPath = "YOUR_DOCUMENT_DIRECTORY/source_directory";
String targetDirectoryPath = "YOUR_DOCUMENT_DIRECTORY/target_directory";
String outputFileName = "YOUR_OUTPUT_DIRECTORY/compare_result.html";
import com.groupdocs.comparison.options.CompareOptions;
import com.groupdocs.comparison.options.enums.FolderComparisonExtension;
CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
compareOptions.setFolderComparisonExtension(FolderComparisonExtension.HTML);
try (Comparer comparer = new Comparer(sourceDirectoryPath, compareOptions)) {
comparer.add(targetDirectoryPath, compareOptions);
comparer.compareDirectory(outputFileName, compareOptions);
System.out.println("Directory comparison completed. Results saved to: " + outputFileName);
} catch (Exception e) {
System.err.println("Comparison failed: " + e.getMessage());
e.printStackTrace();
}
CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
// HTML for human review
compareOptions.setFolderComparisonExtension(FolderComparisonExtension.HTML);
// Or PDF for formal reports
// compareOptions.setFolderComparisonExtension(FolderComparisonExtension.PDF);
CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
// Skip temporary files and build directories
// Note: Exact filtering syntax may vary - check current API documentation
compareOptions.setShowDeletedContent(false); // Don't highlight deleted files
compareOptions.setShowInsertedContent(true); // Do highlight new files
// JVM args: -Xmx4g -Xms2g
// For very large directories, consider processing subdirectories separately
String[] subdirectories = {"subdir1", "subdir2", "subdir3"};
for (String subdir : subdirectories) {
String sourceSub = sourceDirectoryPath + "/" + subdir;
String targetSub = targetDirectoryPath + "/" + subdir;
// Process each subdirectory individually
}
// Better path handling
Path sourcePath = Paths.get(sourceDirectoryPath).toAbsolutePath();
Path targetPath = Paths.get(targetDirectoryPath).toAbsolutePath();
if (!Files.exists(sourcePath)) {
throw new IllegalArgumentException("Source directory doesn't exist: " + sourcePath);
}
if (!Files.exists(targetPath)) {
throw new IllegalArgumentException("Target directory doesn't exist: " + targetPath);
}
// Add progress monitoring
CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
// Log progress (pseudo-code - actual implementation may vary)
long startTime = System.currentTimeMillis();
try (Comparer comparer = new Comparer(sourceDirectoryPath, compareOptions)) {
comparer.add(targetDirectoryPath, compareOptions);
comparer.compareDirectory(outputFileName, compareOptions);
long duration = System.currentTimeMillis() - startTime;
System.out.println("Comparison completed in: " + (duration / 1000) + " seconds");
}
// Increase heap size via JVM arguments
// -Xmx8g (for 8GB max heap)
// -XX:+UseG1GC (for better garbage collection with large heaps)
// In your code, help the GC by nulling large objects
CompareOptions compareOptions = new CompareOptions();
try (Comparer comparer = new Comparer(sourceDirectoryPath, compareOptions)) {
// ... do comparison
comparer.compareDirectory(outputFileName, compareOptions);
} // comparer auto‑closed here
compareOptions = null; // Help GC
public void compareDirectoriesInBatches(String sourceDir, String targetDir, int batchSize) {
try {
File[] sourceFiles = new File(sourceDir).listFiles();
if (sourceFiles != null) {
for (int i = 0; i < sourceFiles.length; i += batchSize) {
int end = Math.min(i + batchSize, sourceFiles.length);
processBatch(sourceFiles, i, end, targetDir);
// Optional: pause between batches to prevent system overload
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Batch processing interrupted", e);
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<String>> futures = new ArrayList<>();
for (DirectoryPair pair : directoryPairs) {
Future<String> future = executor.submit(() -> {
// Perform comparison for this pair
return compareDirectoryPair(pair.source, pair.target);
});
futures.add(future);
}
// Wait for all comparisons to complete
for (Future<String> future : futures) {
try {
String result = future.get();
System.out.println("Comparison result: " + result);
} catch (Exception e) {
System.err.println("Comparison failed: " + e.getMessage());
}
}
executor.shutdown();
// Automated pre-deployment check
String stagingConfig = "/app/staging/config";
String productionConfig = "/app/production/config";
String reportPath = "/reports/deployment-check-" + LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE) + ".html";
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
options.setFolderComparisonExtension(FolderComparisonExtension.HTML);
try (Comparer comparer = new Comparer(stagingConfig, options)) {
comparer.add(productionConfig, options);
comparer.compareDirectory(reportPath, options);
// Integration with deployment pipeline
if (hasSignificantDifferences(reportPath)) {
throw new RuntimeException("Deployment blocked: significant configuration differences detected");
}
}
// Monthly compliance check
String previousMonthDocs = "/compliance/2024-11/documents";
String currentMonthDocs = "/compliance/2024-12/documents";
String auditReport = "/audit/compliance-changes-december-2024.html";
// Compare and generate audit‑ready reports
performComplianceComparison(previousMonthDocs, currentMonthDocs, auditReport);
public boolean verifyDataMigration(String sourceDataDir, String migratedDataDir) {
try {
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
String tempReport = "/tmp/migration-verification.html";
try (Comparer comparer = new Comparer(sourceDataDir, options)) {
comparer.add(migratedDataDir, options);
comparer.compareDirectory(tempReport, options);
}
// Custom logic to parse results and determine if migration was successful
return analyzeComparisonResults(tempReport);
} catch (Exception e) {
System.err.println("Migration verification failed: " + e.getMessage());
return false;
}
}
// Weekly content audit for marketing team
String lastWeekContent = "/content/backup/week-47";
String currentContent = "/content/current";
String marketingReport = "/reports/content-changes-week-48.html";
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
options.setFolderComparisonExtension(FolderComparisonExtension.HTML);
// Generate human‑readable report for non‑technical stakeholders
generateContentChangeReport(lastWeekContent, currentContent, marketingReport, options);
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(DirectoryComparer.class);
public void compareWithLogging(String source, String target, String output) {
logger.info("Starting directory comparison: {} vs {}", source, target);
long startTime = System.currentTimeMillis();
try {
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
try (Comparer comparer = new Comparer(source, options)) {
comparer.add(target, options);
comparer.compareDirectory(output, options);
}
long duration = System.currentTimeMillis() - startTime;
logger.info("Comparison completed successfully in {}ms. Report: {}", duration, output);
} catch (Exception e) {
logger.error("Directory comparison failed for {} vs {}: {}", source, target, e.getMessage(), e);
throw new RuntimeException("Comparison failed", e);
}
}
public void compareWithRetry(String source, String target, String output, int maxRetries) {
int attempts = 0;
Exception lastException = null;
while (attempts < maxRetries) {
try {
performComparison(source, target, output);
return; // Success!
} catch (Exception e) {
lastException = e;
attempts++;
if (attempts < maxRetries) {
try {
Thread.sleep(1000 * attempts); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", ie);
}
}
}
}
throw new RuntimeException("Comparison failed after " + maxRetries + " attempts", lastException);
}
// application.properties
comparison.output.format=HTML
comparison.max.retries=3
comparison.batch.size=100
comparison.parallel.threads=4
// In your code
@Value("${comparison.output.format:HTML}")
private String outputFormat;
@Value("${comparison.max.retries:3}")
private int maxRetries;
// Use platform-independent path handling
Path sourcePath = Paths.get(sourceDirectory);
Path targetPath = Paths.get(targetDirectory);
Path outputPath = Paths.get(outputDirectory);
// Validate permissions before starting
if (!Files.isReadable(sourcePath)) {
throw new IllegalStateException("Cannot read source directory: " + sourcePath);
}
if (!Files.isReadable(targetPath)) {
throw new IllegalStateException("Cannot read target directory: " + targetPath);
}
if (!Files.isWritable(outputPath.getParent())) {
throw new IllegalStateException("Cannot write to output directory: " + outputPath.getParent());
}
CompareOptions options = new CompareOptions();
options.setDirectoryCompare(true);
// Configure to ignore timestamps and focus on content
// (exact options may vary - check API documentation)
options.setIgnoreWhitespaces(true);
options.setIgnoreFormatting(true);