This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat-jakartaee-migration.git


The following commit(s) were added to refs/heads/main by this push:
     new c06f68f  Improve same file comparison robustness
c06f68f is described below

commit c06f68f13f28448b1bea8a79bcc6ba4966490dcf
Author: remm <[email protected]>
AuthorDate: Wed Sep 9 11:33:13 2026 +0200

    Improve same file comparison robustness
    
    From code review.
---
 .../org/apache/tomcat/jakartaee/Migration.java     | 43 ++++++++++++++++++++--
 .../org/apache/tomcat/jakartaee/MigrationTest.java | 32 ++++++++++++++++
 2 files changed, 71 insertions(+), 4 deletions(-)

diff --git a/src/main/java/org/apache/tomcat/jakartaee/Migration.java 
b/src/main/java/org/apache/tomcat/jakartaee/Migration.java
index 5f00ab4..4b760ef 100644
--- a/src/main/java/org/apache/tomcat/jakartaee/Migration.java
+++ b/src/main/java/org/apache/tomcat/jakartaee/Migration.java
@@ -24,7 +24,8 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.attribute.FileTime;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
@@ -333,7 +334,12 @@ public class Migration {
     }
 
     private void migrateFile(File src, File dest) throws IOException {
-        if (src.equals(dest)) {
+        // If the source and destination are the same file, the migrated
+        // content is buffered first and only written to the destination
+        // (which truncates the file) after a successful migration. The
+        // same physical file may be identified by different path strings
+        // so File.equals() is not sufficient to detect this case.
+        if (isSameFile(src, dest)) {
             if (src.length() > TEMP_FILE_THRESHOLD) {
                 // For very large files, use a temp file instead of memory
                 File tempFile = createTempFile();
@@ -392,6 +398,33 @@ public class Migration {
     }
 
 
+    private boolean isSameFile(File src, File dest) throws IOException {
+        // File.equals() only compares path strings. The same physical file
+        // may be addressed by different path strings (e.g. differing use of
+        // "." or ".." components, a symlink or, on case-insensitive file
+        // systems, a different case) and File.equals() would not detect that.
+        // That must be detected, otherwise opening the destination for
+        // writing would truncate the source before it is read.
+        if (src.equals(dest)) {
+            return true;
+        }
+        // Files.isSameFile() requires both files to exist. If the
+        // destination does not exist, it cannot refer to the same file as
+        // the source.
+        if (dest.exists()) {
+            try {
+                return Files.isSameFile(src.toPath(), dest.toPath());
+            } catch (NoSuchFileException e) {
+                // The file disappeared between the check above and this call.
+                // Treat as different files and report the missing file when it
+                // is opened.
+                return false;
+            }
+        }
+        return false;
+    }
+
+
     private boolean migrateArchiveStreaming(InputStream src, OutputStream 
dest) throws IOException {
         boolean convertedArchive = false;
         try (ZipArchiveInputStream srcZipStream = new 
ZipArchiveInputStream(CloseShieldInputStream.wrap(src));
@@ -833,8 +866,10 @@ public class Migration {
         SourceSpool(EESpecProfile profile) throws IOException {
             try {
                 digest = MessageDigest.getInstance("SHA-256");
-                // Include profile name in hash to differentiate between 
profiles
-                
digest.update(profile.toString().getBytes(StandardCharsets.UTF_8));
+                // The keying data (tool version and profile definition) must
+                // be identical to the one used by MigrationCache so hashes
+                // computed here match those used for cache storage/lookup
+                digest.update(MigrationCache.getHashKeyData(profile));
             } catch (NoSuchAlgorithmException e) {
                 throw new IOException(sm.getString("cache.hashError"), e);
             }
diff --git a/src/test/java/org/apache/tomcat/jakartaee/MigrationTest.java 
b/src/test/java/org/apache/tomcat/jakartaee/MigrationTest.java
index 6eb57ab..912a7ad 100644
--- a/src/test/java/org/apache/tomcat/jakartaee/MigrationTest.java
+++ b/src/test/java/org/apache/tomcat/jakartaee/MigrationTest.java
@@ -107,6 +107,38 @@ public class MigrationTest {
         assertTrue("Migrated imports not found", 
migratedSource.contains("import jakarta.servlet"));
     }
 
+    /**
+     * The source and destination may identify the same physical file via
+     * different path strings (relative vs absolute components, symlinks,
+     * differing case on case-insensitive file systems). The migration must
+     * treat it as an in-place migration (migrate before overwriting),
+     * otherwise the destination is truncated before the source is read and
+     * all data is lost. This is a regression test for that behaviour.
+     */
+    @Test
+    public void testMigrateSingleSourceFileInPlaceWithAlternativePath() throws 
Exception {
+        File sourceFile = tempFolder.newFile("HelloServlet.alt.inplace.java");
+        FileUtils.copyFile(new File("target/test-classes/HelloServlet.java"), 
sourceFile);
+        String original = FileUtils.readFileToString(sourceFile, 
StandardCharsets.UTF_8);
+        assertTrue(original.contains("import javax.servlet"));
+
+        // Build a destination path string that differs from the source path
+        // (the extra "." component) but resolves to the same physical file
+        File destinationFile = new File(new File(sourceFile.getParentFile(), 
"."), sourceFile.getName());
+        assertFalse("Test set-up error - paths must differ", 
destinationFile.equals(sourceFile));
+
+        Migration migration = new Migration();
+        migration.setSource(sourceFile);
+        migration.setDestination(destinationFile);
+        migration.execute();
+
+        // The source (same physical file as the destination) must have been
+        // migrated in place, not truncated by the destination stream
+        String migratedSource = FileUtils.readFileToString(sourceFile, 
StandardCharsets.UTF_8);
+        assertFalse("Imports not migrated", migratedSource.contains("import 
javax.servlet"));
+        assertTrue("Migrated imports not found", 
migratedSource.contains("import jakarta.servlet"));
+    }
+
     @Test
     public void testInvalidOption() throws Exception {
         File sourceFile = new File("target/test-classes/HelloServlet.java");


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to