Repository: OpenRefine/OpenRefine (java)

Issue:
CSV files with BOM don't correctly strip BOM on import
Steps to reproduce:
1) Import CSV file [testUNID.zip](https://github.com/OpenRefine/OpenRefine/files/1266702/testUNID.zip)
2) Set encoding to UTF-8
3) Open project
4) Click "Edit column -> Add column based on this column"
5) In "New column name" type "Foo"
6) In "Expression" type cells["UNID"].value

Value is not loaded because the column name is prepended with a Byte Order Mark (BOM).


Pull request:
CSV Byte Order Mark (BOM) support. Fixes #1241
Fixes #1241

* UTF-8-BOM to the encoding guesser
* Add support for stripping the BOM to the importer


Patch:
diff --git a/main/src/com/google/refine/importers/TextFormatGuesser.java b/main/src/com/google/refine/importers/TextFormatGuesser.java
index ca9c6f96b..b9a08a674 100644
--- a/main/src/com/google/refine/importers/TextFormatGuesser.java
+++ b/main/src/com/google/refine/importers/TextFormatGuesser.java
@@ -34,13 +34,13 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
-import java.util.Arrays;
 
 import org.apache.commons.io.input.BoundedInputStream;
 import org.apache.commons.lang.StringUtils;
 
 import com.google.common.base.CharMatcher;
 import com.google.refine.importing.FormatGuesser;
+import com.google.refine.importing.ImportingUtilities;
 
 public class TextFormatGuesser implements FormatGuesser {
 
@@ -51,7 +51,7 @@ public class TextFormatGuesser implements FormatGuesser {
     @Override
     public String guess(File file, String encoding, String seedFormat) {
         try (InputStream fis = new FileInputStream(file)) {
-            if (isCompressed(file)) {
+            if (ImportingUtilities.isCompressed(file)) {
                 return "binary";
             }
             ;
@@ -132,19 +132,4 @@ public class TextFormatGuesser implements FormatGuesser {
         return null;
     }
 
-    private boolean isCompressed(File file) throws IOException {
-        // Check for common compressed file types to protect ourselves from binary data
-        try (InputStream is = new FileInputStream(file)) {
-            byte[] magic = new byte[4];
-            int count = is.read(magic);
-            if (count == 4 && Arrays.equals(magic, new byte[] { 0x50, 0x4B, 0x03, 0x04 }) || // zip
-                    Arrays.equals(magic, new byte[] { 0x50, 0x4B, 0x07, 0x08 }) ||
-                    (magic[0] == 0x1F && magic[1] == (byte) 0x8B) // gzip
-            ) {
-                return true;
-            }
-        }
-        return false;
-    }
-
 }
diff --git a/main/src/com/google/refine/importing/EncodingGuesser.java b/main/src/com/google/refine/importing/EncodingGuesser.java
index 605f93c47..797c355f1 100644
--- a/main/src/com/google/refine/importing/EncodingGuesser.java
+++ b/main/src/com/google/refine/importing/EncodingGuesser.java
@@ -1,31 +1,32 @@
 
 package com.google.refine.importing;
 
-import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.List;
 
-import org.apache.commons.lang.StringUtils;
-import org.mozilla.universalchardet.UniversalDetector;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.commons.lang.StringUtils;
+import org.mozilla.universalchardet.UnicodeBOMInputStream;
+import org.mozilla.universalchardet.UniversalDetector;
 
 import com.google.refine.util.JSONUtilities;
 
 /**
- * This class tries to find the correct encoding based on the http://site.icu-project.org/ and the icu4j library
- * http://site.icu-project.org/home/why-use-icu4j.
+ * This class tries to find the correct encoding based on https://github.com/albfernandez/juniversalchardet which is a
+ * Java port of Mozilla's universalchardet library
+ * https://hg.mozilla.org/mozilla-central/file/tip/extensions/universalchardet/
  * 
  * @author <a href="mailto:kontakt@stundzig.de">Steffen Stundzig</a>
  */
 public final class EncodingGuesser {
 
-    public final static void guess(final ImportingJob job)
-            throws FileNotFoundException, IOException {
+    public static final String UTF_8_BOM = "UTF-8-BOM";
+
+    public static void guess(final ImportingJob job)
+            throws IOException {
         ObjectNode retrievalRecord = job.getRetrievalRecord();
         if (retrievalRecord != null) {
             ArrayNode fileRecords = JSONUtilities.getArray(retrievalRecord, "files");
@@ -38,9 +39,13 @@ public final class EncodingGuesser {
                     if (StringUtils.isBlank(encoding)) {
                         String location = JSONUtilities.getString(record, "location", null);
                         if (location != null) {
-                            try (InputStream is = new BufferedInputStream(
+                            try (UnicodeBOMInputStream is = new UnicodeBOMInputStream(
                                     new FileInputStream(new File(job.getRawDataDir(), location)))) {
                                 String detected = UniversalDetector.detectCharset(is);
+                                UnicodeBOMInputStream.BOM bom = is.getBOM();
+                                if (UnicodeBOMInputStream.BOM.UTF_8.equals(bom)) {
+                                    detected = UTF_8_BOM;
+                                }
                                 if (detected != null) {
                                     JSONUtilities.safePut(record, "encoding", detected);
                                 }
diff --git a/main/src/com/google/refine/importing/ImportingUtilities.java b/main/src/com/google/refine/importing/ImportingUtilities.java
index 27ff6a5d8..5a2f2d923 100644
--- a/main/src/com/google/refine/importing/ImportingUtilities.java
+++ b/main/src/com/google/refine/importing/ImportingUtilities.java
@@ -33,6 +33,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 package com.google.refine.importing;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -69,7 +71,6 @@ import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
 import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
-import org.apache.commons.compress.compressors.lzma.LZMACompressorInputStream;
 import org.apache.commons.fileupload.FileItem;
 import org.apache.commons.fileupload.FileUploadException;
 import org.apache.commons.fileupload.ProgressListener;
@@ -84,6 +85,7 @@ import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.mozilla.universalchardet.UnicodeBOMInputStream;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -349,7 +351,6 @@ public class ImportingUtilities {
                         if (httpClient.getResponse(urlString, null, responseHandler) != null) {
                             archiveCount++;
                         }
-                        ;
                         downloadCount++;
                     } else {
                         // Fallback handling for non HTTP connections (only FTP?)
@@ -435,19 +436,19 @@ public class ImportingUtilities {
 
         update.totalExpectedSize += length;
 
-        progress.setProgress("Downloading " + url.toString(),
+        progress.setProgress("Downloading " + url, // TODO: Localize
                 calculateProgressPercent(update.totalExpectedSize, update.totalRetrievedSize));
 
         long actualLength = saveStreamToFile(stream, file, update);
         JSONUtilities.safePut(fileRecord, "size", actualLength);
         if (actualLength == 0) {
-            throw new IOException("No content found in " + url.toString());
+            throw new IOException("No content found in " + url);
         } else if (length >= 0) {
             update.totalExpectedSize += (actualLength - length);
         } else {
             update.totalExpectedSize += actualLength;
         }
-        progress.setProgress("Saving " + url.toString() + " locally",
+        progress.setProgress("Saving " + url + " locally", // TODO: Localize
                 calculateProgressPercent(update.totalExpectedSize, update.totalRetrievedSize));
         return postProcessRetrievedFile(rawDataDir, file, fileRecord, fileRecords, progress);
     }
@@ -528,11 +529,23 @@ public class ImportingUtilities {
             encoding = commonEncoding;
         }
         if (encoding != null) {
-            try {
-                return new InputStreamReader(inputStream, encoding);
-            } catch (UnsupportedEncodingException e) {
-                // Ignore and fall through
+
+            // Special case for UTF-8 with BOM
+            if (EncodingGuesser.UTF_8_BOM.equals(encoding)) {
+                try {
+                    return new InputStreamReader(new UnicodeBOMInputStream(inputStream, true), UTF_8);
+                } catch (IOException e) {
+                    throw new RuntimeException("Exception from UnicodeBOMInputStream", e);
+                }
+            } else {
+                try {
+                    return new InputStreamReader(inputStream, encoding);
+                } catch (UnsupportedEncodingException e) {
+                    // This should never happen since they picked from a list of supported encodings
+                    throw new RuntimeException("Unsupported encoding: " + encoding, e);
+                }
             }
+
         }
         return new InputStreamReader(inputStream);
     }
@@ -677,7 +690,7 @@ public class ImportingUtilities {
                     || "application/x-zip-compressed".equals(contentType)
                     || "application/zip".equals(contentType)
                     || "application/x-compressed".equals(contentType)
-                    || "multipar/x-zip".equals(contentType)) {
+                    || "multipart/x-zip".equals(contentType)) {
                 return new ZipInputStream(new FileInputStream(file));
             } else if (fileName.endsWith(".kmz")) {
                 return new ZipInputStream(new FileInputStream(file));
@@ -689,13 +702,28 @@ public class ImportingUtilities {
 
     private static boolean isFileGZipped(File file) {
         int magic = 0;
-        try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
+        try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
             magic = raf.read() & 0xff | ((raf.read() << 8) & 0xff00);
         } catch (IOException ignored) {
         }
         return magic == GZIPInputStream.GZIP_MAGIC;
     }
 
+    public static boolean isCompressed(File file) throws IOException {
+        // Check for common compressed file types to protect ourselves from binary data
+        try (InputStream is = new FileInputStream(file)) {
+            byte[] magic = new byte[4];
+            int count = is.read(magic);
+            if (count == 4 && Arrays.equals(magic, new byte[] { 0x50, 0x4B, 0x03, 0x04 }) || // zip
+                    Arrays.equals(magic, new byte[] { 0x50, 0x4B, 0x07, 0x08 }) ||
+                    (magic[0] == 0x1F && magic[1] == (byte) 0x8B) // gzip
+            ) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     // FIXME: This is wasteful of space and time. We should try to process on the fly
     static private boolean explodeArchive(
             File rawDataDir,
@@ -888,7 +916,7 @@ public class ImportingUtilities {
                 }
             }
 
-            // If nothing matches the best format but we have some files,
+            // If nothing matches the best format, but we have some files,
             // then select them all
             if (fileSelectionIndexes.size() == 0 && count > 0) {
                 for (int i = 0; i < count; i++) {
diff --git a/main/webapp/package-lock.json b/main/webapp/package-lock.json
index 824f48858..75ca6ca17 100644
--- a/main/webapp/package-lock.json
+++ b/main/webapp/package-lock.json
@@ -9,7 +9,7 @@
       "dependencies": {
         "@wikimedia/jquery.i18n": "1.0.9",
         "datejs-coolite": "1.0.0",
-        "jquery": "3.7.0",
+        "jquery": "3.7.1",
         "jquery-migrate": "3.4.1",
         "js-cookie": "3.0.5",
         "moment": "2.29.4",
@@ -35,9 +35,9 @@
       "integrity": "sha512-kppYUwn9R2pzsoChLsDlWLtKTwjf5k9Z3fx8CNKjwvhYfpadKIFoL+16dDJvftiXKwT+ixh2PRslZsfzp+1ZVA=="
     },
     "node_modules/jquery": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
-      "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+      "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
     },
     "node_modules/jquery-migrate": {
       "version": "3.4.1",
@@ -94,9 +94,9 @@
       "integrity": "sha512-kppYUwn9R2pzsoChLsDlWLtKTwjf5k9Z3fx8CNKjwvhYfpadKIFoL+16dDJvftiXKwT+ixh2PRslZsfzp+1ZVA=="
     },
     "jquery": {
-      "version": "3.7.0",
-      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
-      "integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
+      "version": "3.7.1",
+      "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
+      "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
     },
     "jquery-migrate": {
       "version": "3.4.1",
diff --git a/main/webapp/package.json b/main/webapp/package.json
index 52f8ccdc4..44211bc1b 100644
--- a/main/webapp/package.json
+++ b/main/webapp/package.json
@@ -12,7 +12,7 @@
   "dependencies": {
     "@wikimedia/jquery.i18n": "1.0.9",
     "datejs-coolite": "1.0.0",
-    "jquery": "3.7.0",
+    "jquery": "3.7.1",
     "jquery-migrate": "3.4.1",
     "js-cookie": "3.0.5",
     "moment": "2.29.4",


Test patch:
diff --git a/main/tests/cypress/package.json b/main/tests/cypress/package.json
index bb2544479..9e190c38d 100644
--- a/main/tests/cypress/package.json
+++ b/main/tests/cypress/package.json
@@ -20,10 +20,10 @@
         "cypress-file-upload": "^5.0.8"
     },
     "devDependencies": {
-        "eslint": "^8.47.0",
+        "eslint": "^8.48.0",
         "eslint-config-google": "^0.14.0",
         "eslint-config-prettier": "^9.0.0",
         "eslint-plugin-cypress": "^2.14.0",
-        "prettier": "3.0.2"
+        "prettier": "3.0.3"
     }
 }
diff --git a/main/tests/cypress/yarn.lock b/main/tests/cypress/yarn.lock
index 74b9f5efc..116e2bbbf 100644
--- a/main/tests/cypress/yarn.lock
+++ b/main/tests/cypress/yarn.lock
@@ -66,10 +66,10 @@
     minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
-"@eslint/js@^8.47.0":
-  version "8.47.0"
-  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.47.0.tgz#5478fdf443ff8158f9de171c704ae45308696c7d"
-  integrity sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==
+"@eslint/js@8.48.0":
+  version "8.48.0"
+  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.48.0.tgz#642633964e217905436033a2bd08bf322849b7fb"
+  integrity sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==
 
 "@humanwhocodes/config-array@^0.11.10":
   version "0.11.10"
@@ -590,15 +590,15 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
   integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
 
-eslint@^8.47.0:
-  version "8.47.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.47.0.tgz#c95f9b935463fb4fad7005e626c7621052e90806"
-  integrity sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==
+eslint@^8.48.0:
+  version "8.48.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.48.0.tgz#bf9998ba520063907ba7bfe4c480dc8be03c2155"
+  integrity sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==
   dependencies:
     "@eslint-community/eslint-utils" "^4.2.0"
     "@eslint-community/regexpp" "^4.6.1"
     "@eslint/eslintrc" "^2.1.2"
-    "@eslint/js" "^8.47.0"
+    "@eslint/js" "8.48.0"
     "@humanwhocodes/config-array" "^0.11.10"
     "@humanwhocodes/module-importer" "^1.0.1"
     "@nodelib/fs.walk" "^1.2.8"
@@ -1303,10 +1303,10 @@ prelude-ls@^1.2.1:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
-prettier@3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b"
-  integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==
+prettier@3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643"
+  integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
 
 pretty-bytes@^5.6.0:
   version "5.6.0"
diff --git a/main/tests/data/csv-with-bom.csv b/main/tests/data/csv-with-bom.csv
new file mode 100644
index 000000000..bdb3bcc64
--- /dev/null
+++ b/main/tests/data/csv-with-bom.csv
@@ -0,0 +1,3 @@
+﻿ColA,ColB
+A1,B1
+A2,B2
\ No newline at end of file
diff --git a/main/tests/server/src/com/google/refine/importing/EncodingGuesserTests.java b/main/tests/server/src/com/google/refine/importing/EncodingGuesserTests.java
index 73d60f011..d5e1a060a 100644
--- a/main/tests/server/src/com/google/refine/importing/EncodingGuesserTests.java
+++ b/main/tests/server/src/com/google/refine/importing/EncodingGuesserTests.java
@@ -15,7 +15,7 @@ import com.google.refine.util.ParsingUtilities;
 
 public class EncodingGuesserTests {
 
-    // Guessing isn't as reliable for single-byte encodings, so we focus on a few multi-byte
+    // Guessing isn't as reliable for single-byte encodings, so we focus on a few multibyte
     // non-UTF8 encodings which are still in use (but <1% prevalence on web)
     static String[] ENCODINGS = {
             "big5",
@@ -25,7 +25,7 @@ public class EncodingGuesserTests {
     };
 
     private static File getTestDir() {
-        String dir = ClassLoader.getSystemResource(ENCODINGS[0] + ".html").getPath();
+        String dir = ClassLoader.getSystemResource(ENCODINGS[0] + ".txt").getPath();
         dir = dir.substring(0, dir.lastIndexOf('/'));
         return new File(dir);
     }
@@ -46,21 +46,29 @@ public class EncodingGuesserTests {
     public void testEncodingGuesser() throws IOException {
 
         for (String encoding : ENCODINGS) {
-            ImportingJob job = new ImportingJobStub();
-            ObjectNode config = job.getOrCreateDefaultConfig();
-            ObjectNode filesObj = ParsingUtilities.evaluateJsonStringToObjectNode(
-                    String.format("{ \"files\": [ {\"location\": \"%s.txt\"}]}", encoding));
-            JSONUtilities.safePut(config, "retrievalRecord", filesObj);
-
-            EncodingGuesser.guess(job);
-
-            ObjectNode retrievalRecord = job.getRetrievalRecord();
-            assertNotNull(retrievalRecord);
-            ArrayNode fileRecords = JSONUtilities.getArray(retrievalRecord, "files");
-            assertNotNull(fileRecords);
-            assertEquals(fileRecords.size(), 1);
-            ObjectNode record = JSONUtilities.getObjectElement(fileRecords, 0);
-            assertEquals(JSONUtilities.getString(record, "encoding", null).toLowerCase(), encoding);
+            checkEncoding(encoding + ".txt", encoding);
         }
+
+        checkEncoding("example-latin1.tsv", "windows-1252"); // close enough - these overlap a lot
+        checkEncoding("example-utf8.tsv", "utf-8");
+        checkEncoding("csv-with-bom.csv", "utf-8-bom");
+    }
+
+    private void checkEncoding(String filename, String encoding) throws IOException {
+        ImportingJob job = new ImportingJobStub();
+        ObjectNode config = job.getOrCreateDefaultConfig();
+        ObjectNode filesObj = ParsingUtilities.evaluateJsonStringToObjectNode(
+                String.format("{ \"files\": [ {\"location\": \"%s\"}]}", filename));
+        JSONUtilities.safePut(config, "retrievalRecord", filesObj);
+
+        EncodingGuesser.guess(job);
+
+        ObjectNode retrievalRecord = job.getRetrievalRecord();
+        assertNotNull(retrievalRecord);
+        ArrayNode fileRecords = JSONUtilities.getArray(retrievalRecord, "files");
+        assertNotNull(fileRecords);
+        assertEquals(fileRecords.size(), 1);
+        ObjectNode record = JSONUtilities.getObjectElement(fileRecords, 0);
+        assertEquals(JSONUtilities.getString(record, "encoding", null).toLowerCase(), encoding);
     }
 }
diff --git a/main/tests/server/src/com/google/refine/importing/ImportingUtilitiesTests.java b/main/tests/server/src/com/google/refine/importing/ImportingUtilitiesTests.java
index 19fc71ce8..0de1291b2 100644
--- a/main/tests/server/src/com/google/refine/importing/ImportingUtilitiesTests.java
+++ b/main/tests/server/src/com/google/refine/importing/ImportingUtilitiesTests.java
@@ -56,7 +56,6 @@ import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
 import org.apache.hc.client5.http.entity.mime.StringBody;
 import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpEntity;
-import org.checkerframework.checker.units.qual.A;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -110,7 +109,7 @@ public class ImportingUtilitiesTests extends ImporterTest {
         File dirA = new File(tempDir, "a");
         dirA.mkdir();
         File conflicting = new File(dirA, "dummy");
-        conflicting.createNewFile();
+        Assert.assertTrue(conflicting.createNewFile());
 
         File allocated = ImportingUtilities.allocateFile(dirA, ".././a/dummy");
         Assert.assertEquals(allocated, new File(dirA, "dummy-2"));