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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 3cdfd5bd386c CAMEL-25303: camel-barcode - keep the text that 
ISO-8859-1 cannot represent, and the hints added before the data format starts 
(#27344)
3cdfd5bd386c is described below

commit 3cdfd5bd386c13c788b691381041ff87b592f27c
Author: allthingssecurity <[email protected]>
AuthorDate: Mon Oct 5 22:34:29 2026 +0530

    CAMEL-25303: camel-barcode - keep the text that ISO-8859-1 cannot 
represent, and the hints added before the data format starts (#27344)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../camel/catalog/docs/barcode-dataformat.adoc     |   3 +-
 .../src/main/docs/barcode-dataformat.adoc          |   3 +-
 .../dataformat/barcode/BarcodeDataFormat.java      |  50 ++++++-
 .../barcode/BarcodeDataFormatCharsetTest.java      | 147 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |   9 ++
 5 files changed, 207 insertions(+), 5 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/barcode-dataformat.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/barcode-dataformat.adoc
index 6ec674dd920b..0e6798e72d1a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/barcode-dataformat.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/barcode-dataformat.adoc
@@ -54,7 +54,7 @@ The default values are:
 
 |height |100 px
 
-|encoding |UTF-8
+|encoding |ISO-8859-1 (the ZXing default), or UTF-8 when the text has 
characters that ISO-8859-1 cannot represent. A `CHARACTER_SET` hint overrides 
it. ZXing uses the encoding for QR-Code, Aztec and PDF417 (and for Data Matrix 
only with the `DATA_MATRIX_COMPACT` hint).
 
 |barcode format (BarcodeFormat) |QR-Code
 |=======================================================================
@@ -75,6 +75,7 @@ your BarcodeDataFormat instance:
 code.addToHintMap(DecodeHintType.TRY_HARDER, Boolean.true);
 ----
 
+The hints can be added before the data format is started (for example when the 
route is configured).
 For possible hints, please consult the xzing documentation.
 
 === Marshalling
diff --git a/components/camel-barcode/src/main/docs/barcode-dataformat.adoc 
b/components/camel-barcode/src/main/docs/barcode-dataformat.adoc
index 6ec674dd920b..0e6798e72d1a 100644
--- a/components/camel-barcode/src/main/docs/barcode-dataformat.adoc
+++ b/components/camel-barcode/src/main/docs/barcode-dataformat.adoc
@@ -54,7 +54,7 @@ The default values are:
 
 |height |100 px
 
-|encoding |UTF-8
+|encoding |ISO-8859-1 (the ZXing default), or UTF-8 when the text has 
characters that ISO-8859-1 cannot represent. A `CHARACTER_SET` hint overrides 
it. ZXing uses the encoding for QR-Code, Aztec and PDF417 (and for Data Matrix 
only with the `DATA_MATRIX_COMPACT` hint).
 
 |barcode format (BarcodeFormat) |QR-Code
 |=======================================================================
@@ -75,6 +75,7 @@ your BarcodeDataFormat instance:
 code.addToHintMap(DecodeHintType.TRY_HARDER, Boolean.true);
 ----
 
+The hints can be added before the data format is started (for example when the 
route is configured).
 For possible hints, please consult the xzing documentation.
 
 === Marshalling
diff --git 
a/components/camel-barcode/src/main/java/org/apache/camel/dataformat/barcode/BarcodeDataFormat.java
 
b/components/camel-barcode/src/main/java/org/apache/camel/dataformat/barcode/BarcodeDataFormat.java
index b37c8ad8cda2..995cf47e6790 100644
--- 
a/components/camel-barcode/src/main/java/org/apache/camel/dataformat/barcode/BarcodeDataFormat.java
+++ 
b/components/camel-barcode/src/main/java/org/apache/camel/dataformat/barcode/BarcodeDataFormat.java
@@ -18,8 +18,11 @@ package org.apache.camel.dataformat.barcode;
 
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.EnumMap;
+import java.util.EnumSet;
 import java.util.Map;
+import java.util.Set;
 
 import javax.imageio.ImageIO;
 
@@ -77,6 +80,19 @@ public class BarcodeDataFormat extends ServiceSupport 
implements DataFormat, Dat
      */
     private final Map<DecodeHintType, Object> readerHintMap = new 
EnumMap<>(DecodeHintType.class);
 
+    /**
+     * The hints added by the user, which are applied on top of the optimized 
hints.
+     */
+    private final Map<EncodeHintType, Object> userWriterHintMap = new 
EnumMap<>(EncodeHintType.class);
+    private final Map<DecodeHintType, Object> userReaderHintMap = new 
EnumMap<>(DecodeHintType.class);
+
+    /**
+     * The hints removed by the user, which are removed from the optimized 
hints (also a default hint removed before the
+     * data format is started).
+     */
+    private final Set<EncodeHintType> removedWriterHints = 
EnumSet.noneOf(EncodeHintType.class);
+    private final Set<DecodeHintType> removedReaderHints = 
EnumSet.noneOf(DecodeHintType.class);
+
     /**
      * Create instance with default parameters.
      */
@@ -170,6 +186,12 @@ public class BarcodeDataFormat extends ServiceSupport 
implements DataFormat, Dat
 
         // reader hints
         this.readerHintMap.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
+
+        // the hints removed and added by the user
+        this.writerHintMap.keySet().removeAll(this.removedWriterHints);
+        this.readerHintMap.keySet().removeAll(this.removedReaderHints);
+        this.writerHintMap.putAll(this.userWriterHintMap);
+        this.readerHintMap.putAll(this.userReaderHintMap);
     }
 
     /**
@@ -187,13 +209,21 @@ public class BarcodeDataFormat extends ServiceSupport 
implements DataFormat, Dat
         // set values
         final String type = this.params.getType().toString();
 
+        // ZXing writes the text in ISO-8859-1 unless a character set is 
given, so use UTF-8 for a text that
+        // ISO-8859-1 cannot represent (ZXing then writes an ECI that tells 
the reader the character set)
+        Map<EncodeHintType, Object> hints = writerHintMap;
+        if (!hints.containsKey(EncodeHintType.CHARACTER_SET) && 
!StandardCharsets.ISO_8859_1.newEncoder().canEncode(payload)) {
+            hints = new EnumMap<>(writerHintMap);
+            hints.put(EncodeHintType.CHARACTER_SET, 
StandardCharsets.UTF_8.name());
+        }
+
         // create code image
         final BitMatrix matrix = writer.encode(
                 payload,
                 this.params.getFormat(),
                 this.params.getWidth(),
                 this.params.getHeight(),
-                writerHintMap);
+                hints);
 
         // write image back to stream
         MatrixToImageWriter.writeToStream(matrix, type, stream);
@@ -227,6 +257,8 @@ public class BarcodeDataFormat extends ServiceSupport 
implements DataFormat, Dat
      */
     public final void addToHintMap(final EncodeHintType hintType, final Object 
value) {
         this.writerHintMap.put(hintType, value);
+        this.userWriterHintMap.put(hintType, value);
+        this.removedWriterHints.remove(hintType);
         LOG.info("Added '{}' with value '{}' to writer hint map.", hintType, 
value);
     }
 
@@ -235,27 +267,39 @@ public class BarcodeDataFormat extends ServiceSupport 
implements DataFormat, Dat
      */
     public final void addToHintMap(final DecodeHintType hintType, final Object 
value) {
         this.readerHintMap.put(hintType, value);
+        this.userReaderHintMap.put(hintType, value);
+        this.removedReaderHints.remove(hintType);
     }
 
     /**
-     * Removes a hint from writer (encode) hint map.
+     * Removes a hint from writer (encode) hint map. A hint removed before the 
data format is started, such as a default
+     * hint, is removed when it starts.
      */
     public final void removeFromHintMap(final EncodeHintType hintType) {
+        this.userWriterHintMap.remove(hintType);
+        this.removedWriterHints.add(hintType);
         if (this.writerHintMap.containsKey(hintType)) {
             this.writerHintMap.remove(hintType);
             LOG.info("Removed '{}' from writer hint map.", hintType);
+        } else if (!isStarted()) {
+            LOG.info("Removing '{}' from writer hint map when the data format 
starts.", hintType);
         } else {
             LOG.warn("Could not find encode hint type '{}' in writer hint 
map.", hintType);
         }
     }
 
     /**
-     * Removes a hint from reader (decode) hint map.
+     * Removes a hint from reader (decode) hint map. A hint removed before the 
data format is started, such as a default
+     * hint, is removed when it starts.
      */
     public final void removeFromHintMap(final DecodeHintType hintType) {
+        this.userReaderHintMap.remove(hintType);
+        this.removedReaderHints.add(hintType);
         if (this.readerHintMap.containsKey(hintType)) {
             this.readerHintMap.remove(hintType);
             LOG.info("Removed '{}' from reader hint map.", hintType);
+        } else if (!isStarted()) {
+            LOG.info("Removing '{}' from reader hint map when the data format 
starts.", hintType);
         } else {
             LOG.warn("Could not find decode hint type '{}' in reader hint 
map.", hintType);
         }
diff --git 
a/components/camel-barcode/src/test/java/org/apache/camel/dataformat/barcode/BarcodeDataFormatCharsetTest.java
 
b/components/camel-barcode/src/test/java/org/apache/camel/dataformat/barcode/BarcodeDataFormatCharsetTest.java
new file mode 100644
index 000000000000..64cd34d844ae
--- /dev/null
+++ 
b/components/camel-barcode/src/test/java/org/apache/camel/dataformat/barcode/BarcodeDataFormatCharsetTest.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dataformat.barcode;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import org.apache.camel.Exchange;
+import org.apache.camel.support.DefaultExchange;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+/**
+ * The text of a barcode must come back as it was written, also when 
ISO-8859-1 (the ZXing default) cannot represent it,
+ * and the hints added or removed before the data format is started must be 
used.
+ */
+public class BarcodeDataFormatCharsetTest extends CamelTestSupport {
+
+    @Test
+    void testQRCodeJapaneseText() throws Exception {
+        assertEquals("日本語のテキスト", roundTrip(new BarcodeDataFormat(), 
"日本語のテキスト"));
+    }
+
+    @Test
+    void testQRCodeCyrillicText() throws Exception {
+        assertEquals("Привет, мир", roundTrip(new BarcodeDataFormat(), 
"Привет, мир"));
+    }
+
+    @Test
+    void testAztecCyrillicText() throws Exception {
+        assertEquals("Привет, мир",
+                roundTrip(new BarcodeDataFormat(200, 200, 
BarcodeImageType.PNG, BarcodeFormat.AZTEC), "Привет, мир"));
+    }
+
+    @Test
+    void testPdf417CyrillicText() throws Exception {
+        assertEquals("Привет, мир",
+                roundTrip(new BarcodeDataFormat(400, 200, 
BarcodeImageType.PNG, BarcodeFormat.PDF_417), "Привет, мир"));
+    }
+
+    @Test
+    void testQRCodeLatinText() throws Exception {
+        assertEquals("Hello Camel", roundTrip(new BarcodeDataFormat(), "Hello 
Camel"));
+        assertEquals("Grüße aus Köln", roundTrip(new BarcodeDataFormat(), 
"Grüße aus Köln"));
+    }
+
+    @Test
+    void testHintsAddedBeforeStart() throws Exception {
+        try (BarcodeDataFormat format = new BarcodeDataFormat()) {
+            format.addToHintMap(EncodeHintType.CHARACTER_SET, "UTF-8");
+            format.addToHintMap(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
+            format.start();
+
+            assertEquals("UTF-8", 
format.getWriterHintMap().get(EncodeHintType.CHARACTER_SET));
+            assertEquals(ErrorCorrectionLevel.H, 
format.getWriterHintMap().get(EncodeHintType.ERROR_CORRECTION));
+            assertEquals(Boolean.TRUE, 
format.getReaderHintMap().get(DecodeHintType.PURE_BARCODE));
+            assertEquals(Boolean.TRUE, 
format.getReaderHintMap().get(DecodeHintType.TRY_HARDER));
+        }
+    }
+
+    @Test
+    void testHintsRemovedBeforeStart() throws Exception {
+        BarcodeDataFormat format = new BarcodeDataFormat();
+        // default hints, and a hint added before start
+        format.removeFromHintMap(EncodeHintType.ERROR_CORRECTION);
+        format.removeFromHintMap(DecodeHintType.TRY_HARDER);
+        format.addToHintMap(EncodeHintType.MARGIN, 2);
+        format.removeFromHintMap(EncodeHintType.MARGIN);
+
+        Exchange exchange = new DefaultExchange(context);
+        assertEquals("Hello Camel", roundTrip(format, "Hello Camel", 
exchange));
+        
assertFalse(format.getWriterHintMap().containsKey(EncodeHintType.ERROR_CORRECTION));
+        
assertFalse(format.getWriterHintMap().containsKey(EncodeHintType.MARGIN));
+        
assertFalse(format.getReaderHintMap().containsKey(DecodeHintType.TRY_HARDER));
+        // the QR code was written with the ZXing default error correction 
(L), not the default hint (H)
+        assertEquals("L", 
exchange.getMessage().getHeader("ERROR_CORRECTION_LEVEL"));
+
+        // a hint added again after it was removed is used
+        format.addToHintMap(EncodeHintType.ERROR_CORRECTION, 
ErrorCorrectionLevel.M);
+        exchange = new DefaultExchange(context);
+        assertEquals("Hello Camel", roundTrip(format, "Hello Camel", 
exchange));
+        assertEquals("M", 
exchange.getMessage().getHeader("ERROR_CORRECTION_LEVEL"));
+    }
+
+    @Test
+    void testDefaultHintsWithoutRemoval() throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        assertEquals("Hello Camel", roundTrip(new BarcodeDataFormat(), "Hello 
Camel", exchange));
+        assertEquals("H", 
exchange.getMessage().getHeader("ERROR_CORRECTION_LEVEL"));
+    }
+
+    @Test
+    void testCharacterSetHintIsUsed() throws Exception {
+        BarcodeDataFormat format = new BarcodeDataFormat();
+        format.addToHintMap(EncodeHintType.CHARACTER_SET, "UTF-8");
+
+        Exchange exchange = new DefaultExchange(context);
+        assertEquals("Grüße", roundTrip(format, "Grüße", exchange));
+        // the text was written in UTF-8 as the hint asks, not in ISO-8859-1
+        List<?> segments = exchange.getMessage().getHeader("BYTE_SEGMENTS", 
List.class);
+        assertEquals(1, segments.size());
+        assertArrayEquals("Grüße".getBytes(StandardCharsets.UTF_8), (byte[]) 
segments.get(0));
+    }
+
+    private String roundTrip(BarcodeDataFormat writer, String text) throws 
Exception {
+        return roundTrip(writer, text, new DefaultExchange(context));
+    }
+
+    private String roundTrip(BarcodeDataFormat writer, String text, Exchange 
exchange) throws Exception {
+        try (BarcodeDataFormat reader = new BarcodeDataFormat()) {
+            writer.start();
+            reader.start();
+
+            ByteArrayOutputStream image = new ByteArrayOutputStream();
+            writer.marshal(exchange, text, image);
+            return (String) reader.unmarshal(exchange,
+                    new BufferedInputStream(new 
ByteArrayInputStream(image.toByteArray())));
+        } finally {
+            writer.stop();
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 2170298d9585..8313a4b93338 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -755,6 +755,15 @@ data format also writes the message with the charset of 
the exchange when it mar
 US-ASCII messages are parsed as before. A route that repaired the wrongly 
decoded text itself must stop
 doing so.
 
+=== camel-barcode - text outside ISO-8859-1
+
+The barcode data format wrote the text in ISO-8859-1 (the ZXing default), so 
every character that ISO-8859-1 cannot
+represent (such as Japanese, Cyrillic, Greek or an emoji) was written as `?`. 
Such a text is now written in UTF-8,
+with an ECI segment that tells the reader the character set. A reader without 
ECI support sees the UTF-8 bytes
+instead of the `?` characters, so it must decode them as UTF-8. Text that 
ISO-8859-1 can represent is written as
+before, byte for byte, and a `CHARACTER_SET` hint takes precedence. The 
documented default encoding is now
+"ISO-8859-1, or UTF-8 when needed" instead of "UTF-8".
+
 === camel-bindy - fixed-length records with characters outside the BMP
 
 Since Camel 3.1 a fixed-length record is read by counting code points (or 
graphemes with

Reply via email to