Skip to content

Latest commit

 

History

History
612 lines (458 loc) · 26.9 KB

File metadata and controls

612 lines (458 loc) · 26.9 KB
categories
Java Development
date 2026-03-22
description تعلم كيفية استخدام GroupDocs Comparison Java لمقارنة الأدلة في Java. اتقن تدقيق الملفات، وأتمتة التحكم في الإصدارات، وتحسين الأداء.
keywords java directory comparison tool, groupdocs comparison tutorial, java file audit automation, directory sync java, how to compare folders in java programming
lastmod 2026-03-22
linktitle Java Directory Comparison Guide
tags
directory-comparison
file-audits
groupdocs
java-tutorial
title GroupDocs مقارنة جافا - أداة مقارنة المجلدات في جافا - دليل شامل
type docs
url /ar/java/advanced-comparison/master-directory-comparison-java-groupdocs-comparison/
weight 1

أداة مقارنة الأدلة في جافا - دليل كامل مع GroupDocs.Comparison

المقدمة

هل قضيت ساعات في فحص الملفات التي تغيرت يدويًا بين إصداري مشروع؟ لست وحدك. groupdocs comparison java يجعل هذه المهمة المملة سهلة من خلال السماح لك بمقارنة مجلدين باستدعاء API واحد. مقارنة الأدلة هي واحدة من تلك المهام المرهقة التي يمكن أن تستهلك كامل فترة بعد الظهر — إلا إذا قمت بأتمتتها.

GroupDocs.Comparison for Java يحول هذه النقطة المؤلمة إلى استدعاء API بسيط. سواء كنت تتعقب التغييرات في قاعدة شفرة ضخمة، أو تزامن الملفات عبر بيئات مختلفة، أو تجري تدقيقات امتثال، فإن هذه المكتبة تتولى الجزء الثقيل حتى لا تضطر للقيام به.

في هذا الدليل، ستتعلم كيفية إعداد مقارنات أدلة آلية تعمل فعليًا في سيناريوهات العالم الحقيقي. سنغطي كل شيء من الإعداد الأساسي إلى تحسين الأداء لتلك الأدلة الضخمة التي تحتوي على آلاف الملفات.

ما ستتقنه:

  • إعداد كامل لـ GroupDocs.Comparison (بما في ذلك الفخاخ)
  • تنفيذ مقارنة الأدلة خطوة بخطوة
  • تكوين متقدم لقواعد مقارنة مخصصة
  • تحسين الأداء للمقارنات على نطاق واسع
  • استكشاف الأخطاء الشائعة (لأنها ستحدث)
  • حالات استخدام واقعية عبر صناعات مختلفة

إجابات سريعة

  • ما هي المكتبة الأساسية؟ groupdocs comparison java
  • إصدار Java المدعوم؟ Java 8 أو أعلى
  • الوقت المعتاد للإعداد؟ 10–15 دقيقة لمقارنة أساسية
  • متطلبات الترخيص؟ نعم – يلزم ترخيص تجريبي أو تجاري
  • تنسيقات الإخراج؟ HTML (افتراضي) أو PDF

لماذا مقارنة الأدلة مهمة (أكثر مما تتصور)

قبل الغوص في الشيفرة، دعنا نتحدث عن سبب أهميتها. مقارنة الأدلة ليست مجرد العثور على ملفات مختلفة — إنها تتعلق بالحفاظ على سلامة البيانات، وضمان الامتثال، واكتشاف تلك التغييرات الخفية التي قد تعطل بيئة الإنتاج لديك.

سيناريوهات شائعة ستحتاج فيها إلى ذلك:

  • إدارة الإصدارات: مقارنة أدلة البيئة التجريبية مع الإنتاج قبل النشر
  • ترحيل البيانات: التأكد من نقل جميع الملفات بشكل صحيح بين الأنظمة
  • تدقيق الامتثال: تتبع تغييرات المستندات للمتطلبات التنظيمية
  • التحقق من النسخ الاحتياطي: التأكد من أن عملية النسخ الاحتياطي نجحت فعلاً
  • التعاون الجماعي: تحديد من غير ما تم تغييره في أدلة المشروع المشتركة

المتطلبات المسبقة وإعداد البيئة

قبل أن نبدأ بالبرمجة، تأكد من أن بيئتك جاهزة. إليك ما ستحتاجه (ولماذا):

المتطلبات الأساسية:

  1. Java 8 أو أعلى – يستخدم GroupDocs.Comparison ميزات Java الحديثة
  2. Maven 3.6+ – لإدارة التبعيات (صدقني، لا تحاول إدارة JAR يدويًا)
  3. IDE يدعم Java جيدًا – يُنصح بـ IntelliJ IDEA أو Eclipse
  4. على الأقل 2 GB RAM – يمكن أن تكون مقارنات الأدلة مستهلكة للذاكرة

المعرفة المطلوبة:

  • برمجة Java أساسية (حلقات، شرطيات، معالجة استثناءات)
  • فهم عمليات إدخال/إخراج الملفات
  • إلمام بإدارة تبعيات Maven
  • معرفة أساسية بـ try‑with‑resources (سنستخدمها على نطاق واسع)

اختياري لكن مفيد:

  • خبرة في أطر التسجيل (SLF4J/Logback)
  • فهم مفاهيم الـ multi‑threading
  • معرفة أساسية بـ HTML (لتنسيق الإخراج)

إعداد GroupDocs.Comparison لجافا

لندمج هذه المكتبة بشكل صحيح في مشروعك. الإعداد بسيط، لكن هناك بعض الفخاخ التي يجب الانتباه لها.

تكوين Maven

أضف ما يلي إلى ملف pom.xml الخاص بك – لاحظ تكوين المستودع، الذي يُغفل عنه كثيرًا:

<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>

نصيحة احترافية: استخدم دائمًا أحدث رقم نسخة من موقع GroupDocs. قد لا تكون النسخة المعروضة هنا هي الأحدث.

إعداد الترخيص (لا تتخطى هذه الخطوة)

GroupDocs ليس مجانيًا، لكنهم يقدمون عدة خيارات:

  • تجربة مجانية: تجربة لمدة 30 يومًا مع جميع الميزات (مثالية للتقييم)
  • ترخيص مؤقت: تجربة ممتدة للتطوير/الاختبار
  • ترخيص تجاري: للاستخدام في الإنتاج

احصل على الترخيص من:

التهيئة الأساسية والاختبار

بعد إعداد التبعيات، اختبر التكامل:

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());
        }
    }
}

إذا تم تشغيله دون أخطاء، فأنت جاهز للمتابعة. إذا لم يحدث ذلك، تحقق من تكوين Maven واتصال الإنترنت (GroupDocs يتحقق من الترخيص عبر الإنترنت).

التنفيذ الأساسي: مقارنة الأدلة

الآن للحدث الرئيسي — مقارنة الأدلة فعليًا. سنبدأ بتنفيذ أساسي ثم نضيف ميزات متقدمة.

مقارنة أدلة أساسية

هذا هو التنفيذ الأساسي الذي يغطي معظم الحالات:

الخطوة 1: إعداد المسارات

String sourceDirectoryPath = "YOUR_DOCUMENT_DIRECTORY/source_directory";
String targetDirectoryPath = "YOUR_DOCUMENT_DIRECTORY/target_directory";
String outputFileName = "YOUR_OUTPUT_DIRECTORY/compare_result.html";

مهم: استخدم مسارات مطلقة قدر الإمكان، خاصة في بيئات الإنتاج. قد تتسبب المسارات النسبية في مشاكل اعتمادًا على مكان تشغيل التطبيق.

الخطوة 2: تكوين خيارات المقارنة

import com.groupdocs.comparison.options.CompareOptions;
import com.groupdocs.comparison.options.enums.FolderComparisonExtension;

CompareOptions compareOptions = new CompareOptions();
compareOptions.setDirectoryCompare(true);
compareOptions.setFolderComparisonExtension(FolderComparisonExtension.HTML);

لماذا إخراج HTML؟ تقارير HTML قابلة للقراءة من قبل البشر ويمكن عرضها في أي متصفح. مثالية لمشاركة النتائج مع أصحاب المصلحة غير التقنيين.

الخطوة 3: تنفيذ المقارنة

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();
}

لماذا try‑with‑resources؟ يدير GroupDocs.Comparison مقبض الملفات والذاكرة داخليًا. يضمن استخدام try‑with‑resources تنظيفًا صحيحًا، وهو أمر مهم عند مقارنة أدلة كبيرة.

خيارات تكوين متقدمة

الإعداد الأساسي يعمل، لكن السيناريوهات الواقعية تتطلب تخصيصًا. إليك كيفية ضبط المقارنات بدقة:

تخصيص تنسيقات الإخراج

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

المشكلات الشائعة والحلول

دعنا نتناول المشكلات التي قد تواجهها (لأن قانون مورفي ينطبق على البرمجة أيضًا):

المشكلة 1: OutOfMemoryError مع أدلة كبيرة

الأعراض: يتعطل التطبيق بسبب أخطاء مساحة الذاكرة عند مقارنة أدلة تحتوي على آلاف الملفات.

الحل: زيادة حجم heap للـ JVM ومعالجة الأدلة على دفعات:

// 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
}

المشكلة 2: FileNotFoundException رغم صحة المسارات

الأعراض: تبدو المسارات صحيحة، لكنك تحصل على أخطاء "ملف غير موجود".

الأسباب الشائعة والحلول:

  • الأذونات: تأكد من أن تطبيق Java يملك صلاحية القراءة للمجلدات المصدر وصلاحية الكتابة لموقع الإخراج
  • الأحرف الخاصة: تحتاج أسماء الأدلة التي تحتوي على مسافات أو أحرف خاصة إلى هروب مناسب
  • مسارات الشبكة: قد لا تعمل مسارات UNC كما هو متوقع — انسخ الملفات محليًا أولًا
// 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);
}

المشكلة 3: المقارنة تستغرق وقتًا طويلاً

الأعراض: تستمر المقارنة لساعات دون إكمال.

الحلول:

  1. تصفية الملفات غير الضرورية قبل المقارنة
  2. استخدام الـ multi‑threading للأدلة الفرعية المستقلة
  3. تنفيذ تتبع التقدم لمراقبة ما يحدث
// 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();

حالات استخدام واقعية وتطبيقات صناعية

مقارنة الأدلة ليست مجرد أداة للمطورين — إنها تُستخدم عبر الصناعات لعمليات حيوية للأعمال:

تطوير البرمجيات وDevOps

إدارة الإصدارات: مقارنة أدلة البيئة التجريبية مع الإنتاج قبل النشر لتجنب انزلاق الإعدادات:

// 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);

إدارة البيانات وعمليات ETL

التحقق من سلامة البيانات: ضمان إكمال عمليات ترحيل البيانات بنجاح:

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;
    }
}

إدارة المحتوى والنشر

التحكم في الإصدارات للفرق غير التقنية: يمكن لفرق التسويق والمحتوى تتبع تغييرات المستودعات الوثائقية دون الحاجة إلى Git:

// 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);

استكشاف مشكلات النشر الشائعة

يعمل في التطوير، لكنه يفشل في الإنتاج

الأعراض: تعمل المقارنة محليًا ولكنها تتعطل على الخادم.

الأسباب الجذرية:

  • اختلاف حساسية الأحرف (Windows vs Linux)
  • أذونات نظام الملفات الأكثر صرامة
  • مسارات ثابتة مشفرة (/ vs \)

الحل: استخدم Path و File.separator كما هو موضح في قسم التعامل مع المسارات بشكل مستقل عن المنصة أعلاه.

نتائج غير متسقة

الأعراض: تشغيل نفس المقارنة مرتين ينتج مخرجات مختلفة.

الأسباب المحتملة:

  • تعديل الملفات أثناء التشغيل
  • اعتبار الطوابع الزمنية كاختلافات
  • اختلاف بيانات التعريف في نظام الملفات الأساسي

الحل: اضبط CompareOptions لتجاهل الطوابع الزمنية والتركيز على المحتوى الفعلي (انظر تجاهل الطوابع الزمنية).

الأسئلة المتكررة

س: كيف أتعامل مع أدلة تحتوي على ملايين الملفات؟
ج: اجمع بين المعالجة على دفعات، وزد حجم heap للـ JVM (-Xmx)، وشغّل مقارنات الأدلة الفرعية بالتوازي. أقسام استراتيجية المعالجة على دفعات و المعالجة المتوازية توفر أنماطًا جاهزة للاستخدام.

س: هل يمكنني مقارنة أدلة موجودة على خوادم مختلفة؟
ج: نعم، لكن زمن الاستجابة الشبكي قد يهيمن على وقت التنفيذ. للحصول على أفضل أداء، انسخ الدليل البعيد محليًا قبل استدعاء المقارنة، أو اربط المشاركة البعيدة بكمية كافية من عرض النطاق الترددي I/O.

س: ما هي صيغ الملفات التي يدعمها GroupDocs.Comparison؟
ج: يدعم GroupDocs.Comparison مجموعة واسعة من الصيغ، بما في ذلك DOC/DOCX، PDF، PPT/PPTX، XLS/XLSX، TXT، HTML، وأنواع الصور الشائعة. راجع الوثائق الرسمية للحصول على أحدث القائمة.

س: كيف يمكن دمج هذه المقارنة في خط أنابيب CI/CD؟
ج: غلف منطق المقارنة في مكوّن Maven/Gradle أو ملف JAR مستقل، ثم استدعِه كخطوة بناء في Jenkins، GitHub Actions، Azure Pipelines، إلخ. استخدم مثال التسجيل والمراقبة لإظهار النتائج كملفات بناء.

س: هل يمكن تخصيص مظهر تقرير HTML؟
ج: القالب المدمج للـ HTML ثابت، لكن يمكنك معالجة الملف الناتج لاحقًا (مثلاً، حقن CSS أو JavaScript مخصص) لتتناسب مع هوية العلامة التجارية الخاصة بك.


آخر تحديث: 2026-03-22
تم الاختبار مع: GroupDocs.Comparison 25.2 (Java)
المؤلف: GroupDocs