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

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-csv.git


The following commit(s) were added to refs/heads/master by this push:
     new 80d68871 Fix SpotBugs USO_UNSAFE_METHOD_SYNCHRONIZATION in CSVFormat 
print methods.
80d68871 is described below

commit 80d68871b3ec2e61ac1d5e6fa757528e8224804d
Author: Gary Gregory <[email protected]>
AuthorDate: Thu Jul 16 11:33:47 2026 -0400

    Fix SpotBugs USO_UNSAFE_METHOD_SYNCHRONIZATION in CSVFormat print
    methods.
---
 src/changes/changes.xml                            |  1 +
 .../java/org/apache/commons/csv/CSVFormat.java     | 99 +++++++++++++---------
 2 files changed, 59 insertions(+), 41 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index db22b85d..722e0373 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -67,6 +67,7 @@
       <action type="fix" dev="ggregory" due-to="Dexter.k, Gary Gregory">Quote 
value with leading or trailing Unicode whitespace (#621).</action>
       <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary 
Gregory">Exclude byte-order mark from ExtendedBufferedReader byte counting 
(#622).</action>
       <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary 
Gregory">Skip trailing delimiter on comment lines in printComment 
(#623).</action>
+      <action type="fix" dev="ggregory" due-to="Naveed Khan, Gary Gregory">Fix 
SpotBugs 4.10.3 USO_UNSAFE_METHOD_SYNCHRONIZATION in CSVFormat print 
methods.</action>
       <!-- ADD -->
       <action type="add" dev="ggregory" due-to="Gary Gregory, Indy, Sylvia van 
Os" issue="CSV-307">Add an "Android Compatibility" section to the web 
site.</action>
       <action type="add" dev="ggregory" due-to="Ruiqi Dong, Gary Gregory" 
issue="CSV-325">Add CSVParser.Builder.setByteOffset(long) (#604).</action>
diff --git a/src/main/java/org/apache/commons/csv/CSVFormat.java 
b/src/main/java/org/apache/commons/csv/CSVFormat.java
index dc8de686..4bd68520 100644
--- a/src/main/java/org/apache/commons/csv/CSVFormat.java
+++ b/src/main/java/org/apache/commons/csv/CSVFormat.java
@@ -24,6 +24,8 @@ import static org.apache.commons.io.IOUtils.EOF;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.NotActiveException;
+import java.io.ObjectInputStream;
 import java.io.OutputStream;
 import java.io.Reader;
 import java.io.Serializable;
@@ -1628,6 +1630,8 @@ public final class CSVFormat implements Serializable {
     /** The maximum number of rows to process, excluding the header row. */
     private final long maxRows;
 
+    private transient Object writeLock = new Object();
+
     private CSVFormat(final Builder builder) {
         this.allowMissingColumnNames = builder.allowMissingColumnNames;
         this.autoFlush = builder.autoFlush;
@@ -1720,10 +1724,7 @@ public final class CSVFormat implements Serializable {
         if (this == obj) {
             return true;
         }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
+        if (obj == null || getClass() != obj.getClass()) {
             return false;
         }
         final CSVFormat other = (CSVFormat) obj;
@@ -2233,32 +2234,34 @@ public final class CSVFormat implements Serializable {
      * @throws IOException If an I/O error occurs.
      * @since 1.4
      */
-    public synchronized void print(final Object value, final Appendable out, 
final boolean newRecord) throws IOException {
-        // null values are considered empty
-        // Only call CharSequence.toString() if you have to, helps GC-free use 
cases.
-        CharSequence charSequence;
-        if (value == null) {
-            // https://issues.apache.org/jira/browse/CSV-203
-            if (nullString == null) {
-                charSequence = Constants.EMPTY;
-            } else if (QuoteMode.ALL == quoteMode) {
-                charSequence = quotedNullString;
+    public void print(final Object value, final Appendable out, final boolean 
newRecord) throws IOException {
+        synchronized (writeLock) {
+            // null values are considered empty
+            // Only call CharSequence.toString() if you have to, helps GC-free 
use cases.
+            CharSequence charSequence;
+            if (value == null) {
+                // https://issues.apache.org/jira/browse/CSV-203
+                if (nullString == null) {
+                    charSequence = Constants.EMPTY;
+                } else if (QuoteMode.ALL == quoteMode) {
+                    charSequence = quotedNullString;
+                } else {
+                    charSequence = nullString;
+                }
+            } else if (value instanceof CharSequence) {
+                charSequence = (CharSequence) value;
+            } else if (value instanceof Reader) {
+                print((Reader) value, out, newRecord);
+                return;
+            } else if (value instanceof InputStream) {
+                print((InputStream) value, out, newRecord);
+                return;
             } else {
-                charSequence = nullString;
+                charSequence = value.toString();
             }
-        } else if (value instanceof CharSequence) {
-            charSequence = (CharSequence) value;
-        } else if (value instanceof Reader) {
-            print((Reader) value, out, newRecord);
-            return;
-        } else if (value instanceof InputStream) {
-            print((InputStream) value, out, newRecord);
-            return;
-        } else {
-            charSequence = value.toString();
+            charSequence = getTrim() ? trim(charSequence) : charSequence;
+            print(value, charSequence, out, newRecord);
         }
-        charSequence = getTrim() ? trim(charSequence) : charSequence;
-        print(value, charSequence, out, newRecord);
     }
 
     private synchronized void print(final Object object, final CharSequence 
value, final Appendable out, final boolean newRecord) throws IOException {
@@ -2335,12 +2338,14 @@ public final class CSVFormat implements Serializable {
      * @throws IOException If an I/O error occurs.
      * @since 1.4
      */
-    public synchronized void println(final Appendable appendable) throws 
IOException {
-        if (getTrailingDelimiter()) {
-            append(getDelimiterString(), appendable);
-        }
-        if (recordSeparator != null) {
-            append(recordSeparator, appendable);
+    public void println(final Appendable appendable) throws IOException {
+        synchronized (writeLock) {
+            if (getTrailingDelimiter()) {
+                append(getDelimiterString(), appendable);
+            }
+            if (recordSeparator != null) {
+                append(recordSeparator, appendable);
+            }
         }
     }
 
@@ -2357,11 +2362,13 @@ public final class CSVFormat implements Serializable {
      * @throws IOException If an I/O error occurs.
      * @since 1.4
      */
-    public synchronized void printRecord(final Appendable appendable, final 
Object... values) throws IOException {
-        for (int i = 0; i < values.length; i++) {
-            print(values[i], appendable, i == 0);
+    public void printRecord(final Appendable appendable, final Object... 
values) throws IOException {
+        synchronized (writeLock) {
+            for (int i = 0; i < values.length; i++) {
+                print(values[i], appendable, i == 0);
+            }
+            println(appendable);
         }
-        println(appendable);
     }
 
     /*
@@ -2537,10 +2544,7 @@ public final class CSVFormat implements Serializable {
                         // Some other chars at the end caused the parser to 
fail, so for now
                         // encapsulate if we end in anything less than ' '. A 
trailing Unicode whitespace
                         // above ' ' is stripped by ignoreSurroundingSpaces on 
read, so quote it too.
-                        if (isTrimChar(c) || Character.isWhitespace(c)) {
-                            quote = true;
-                        } else if (endsWithDelimiterPrefix(charSeq, delim, 
delimLength)) {
-                            // A trailing partial multi-character delimiter 
would merge with the following delimiter on read.
+                        if (isTrimChar(c) || Character.isWhitespace(c) || 
endsWithDelimiterPrefix(charSeq, delim, delimLength)) {
                             quote = true;
                         }
                     }
@@ -2607,6 +2611,19 @@ public final class CSVFormat implements Serializable {
         append(quote, appendable);
     }
 
+    /**
+     * Reads the object from the stream and reinitializes transient fields.
+     *
+     * @param in the input stream.
+     * @throws ClassNotFoundException if the class of a serialized object 
could not be found.
+     * @throws IOException            if an I/O error occurs.
+     * @throws NotActiveException     if the stream is not currently reading 
objects.
+     */
+    private void readObject(final ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        in.defaultReadObject();
+        writeLock = new Object();
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();

Reply via email to