DIRSTUDIO-1300: Use LookupTranslator instead of chained replace() calls. (#37)

diff --git a/eclipse-trgt-platform/pom-first.xml b/eclipse-trgt-platform/pom-first.xml
index 22ed568..2ae081e 100644
--- a/eclipse-trgt-platform/pom-first.xml
+++ b/eclipse-trgt-platform/pom-first.xml
@@ -74,6 +74,12 @@
       <version>${org.apache.commons.pool.version}</version>
     </dependency>
 
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-text</artifactId>
+      <version>${org.apache.commons.text.version}</version>
+    </dependency>
+
     <!-- Bouncycastle modules -->
     <dependency>
       <groupId>org.bouncycastle</groupId>
diff --git a/eclipse-trgt-platform/template/org.apache.directory.studio.eclipse-trgt-platform.template b/eclipse-trgt-platform/template/org.apache.directory.studio.eclipse-trgt-platform.template
index 41bdc78..0d19d28 100644
--- a/eclipse-trgt-platform/template/org.apache.directory.studio.eclipse-trgt-platform.template
+++ b/eclipse-trgt-platform/template/org.apache.directory.studio.eclipse-trgt-platform.template
@@ -19,7 +19,7 @@
   @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 -->
 <?pde version="3.8"?>
-<target name="Apache Directory Studio Platform" sequenceNumber="485">
+<target name="Apache Directory Studio Platform" sequenceNumber="486">
   <locations>
 
     <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
@@ -39,6 +39,7 @@
       <unit id="org.apache.commons.io" version="${org.apache.commons.io.bundleversion}"/>
       <unit id="org.apache.commons.lang3" version="${org.apache.commons.lang3.bundleversion}"/>
       <unit id="org.apache.commons.commons-pool2" version="${org.apache.commons.pool.bundleversion}"/>
+      <unit id="org.apache.commons.commons-text" version="${org.apache.commons.text.bundleversion}"/>
 
       <!-- LDAP API modules -->
       <unit id="org.apache.directory.api.asn1.api" version="${org.apache.directory.api.bundleversion}"/>
diff --git a/features/rcp.feature/feature.xml b/features/rcp.feature/feature.xml
index def333d..65e3678 100644
--- a/features/rcp.feature/feature.xml
+++ b/features/rcp.feature/feature.xml
@@ -267,6 +267,13 @@
          unpack="false"/>
 
    <plugin
+         id="org.apache.commons.commons-text"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
+   <plugin
          id="com.github.ben-manes.caffeine"
          download-size="0"
          install-size="0"
diff --git a/plugins/ldapbrowser.core/pom-first.xml b/plugins/ldapbrowser.core/pom-first.xml
index bb34e8d..b279dad 100644
--- a/plugins/ldapbrowser.core/pom-first.xml
+++ b/plugins/ldapbrowser.core/pom-first.xml
@@ -118,6 +118,7 @@
  org.apache.directory.studio.ldapbrowser.core.utils</Export-Package>
  
             <Import-Package>
+ org.apache.commons.text,
  org.apache.commons.lang3,
  org.apache.commons.collections4,
  org.apache.commons.codec.digest,
@@ -127,6 +128,7 @@
             </Import-Package>
             
             <Require-Bundle>
+ org.apache.commons.commons-text,
  org.apache.directory.api.asn1.api;bundle-version="${org.apache.directory.api.bundleversion}",
  org.apache.directory.api.ldap.model;bundle-version="${org.apache.directory.api.bundleversion}",
  org.apache.directory.api.ldap.codec.core;bundle-version="${org.apache.directory.api.bundleversion}",
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportCsvRunnable.java b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportCsvRunnable.java
index e6195ba..e5b9d56 100644
--- a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportCsvRunnable.java
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportCsvRunnable.java
@@ -30,6 +30,7 @@
 import java.util.Map;
 
 import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.text.translate.CharSequenceTranslator;
 import org.apache.directory.api.ldap.model.constants.SchemaConstants;
 import org.apache.directory.api.ldap.model.exception.LdapException;
 import org.apache.directory.api.ldap.model.schema.AttributeType;
@@ -267,6 +268,7 @@
         String[] attributes, String attributeDelimiter, String valueDelimiter, String quoteCharacter,
         String lineSeparator, String encoding, int binaryEncoding, boolean exportDn )
     {
+        CharSequenceTranslator decoder = Utils.createPostalAddressDecoder( lineSeparator );
 
         // group multi-valued attributes
         Map<String, String> attributeMap = getAttributeMap( browserConnection, record, valueDelimiter, encoding,
@@ -294,7 +296,7 @@
                 AttributeType type = browserConnection.getSchema().getAttributeTypeDescription( attributeName );
                 if ( SchemaConstants.POSTAL_ADDRESS_SYNTAX.equals( type.getSyntaxOid() ) )
                 {
-                    value = Utils.decodePostalAddress( value, lineSeparator );
+                    value = decoder.translate( value );
                 }
                 appendValue( quoteCharacter, sb, value );
             }
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportOdfRunnable.java b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportOdfRunnable.java
index abbed1d..b9c40e8 100644
--- a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportOdfRunnable.java
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportOdfRunnable.java
@@ -26,6 +26,7 @@
 import java.util.Map;
 
 import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.text.translate.CharSequenceTranslator;
 import org.apache.directory.api.ldap.model.constants.SchemaConstants;
 import org.apache.directory.api.ldap.model.exception.LdapException;
 import org.apache.directory.api.ldap.model.schema.AttributeType;
@@ -60,6 +61,9 @@
     /** The maximum count limit */
     public static final int MAX_COUNT_LIMIT = 65000;
 
+    /** The postal address decoder. */
+    private static CharSequenceTranslator DECODER = Utils.createPostalAddressDecoder( "\n" ); //$NON-NLS-1$;
+
     /** The filename of the ODF file. */
     private String exportOdfFilename;
 
@@ -297,7 +301,7 @@
                 if ( SchemaConstants.POSTAL_ADDRESS_SYNTAX.equals( type.getSyntaxOid() ) )
                 {
                     // https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html#__RefHeading__1017970_715980110
-                    value = Utils.decodePostalAddress( value, "\n" ); //$NON-NLS-1$
+                    value = DECODER.translate( value );
                     cell.setTextWrapped( true );
                 }
                 cell.setStringValue( value );
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportXlsRunnable.java b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportXlsRunnable.java
index 2030022..3bfaafc 100644
--- a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportXlsRunnable.java
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/jobs/ExportXlsRunnable.java
@@ -27,6 +27,7 @@
 import java.util.Map;
 
 import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.text.translate.CharSequenceTranslator;
 import org.apache.directory.api.ldap.model.constants.SchemaConstants;
 import org.apache.directory.api.ldap.model.exception.LdapException;
 import org.apache.directory.api.ldap.model.schema.AttributeType;
@@ -62,6 +63,9 @@
     /** The maximum count limit */
     public static final int MAX_COUNT_LIMIT = 65000;
 
+    /** The postal address decoder. */
+    private static CharSequenceTranslator DECODER = Utils.createPostalAddressDecoder( "\n" ); //$NON-NLS-1$;
+
     /** The filename of the XLS file. */
     private String exportXlsFilename;
 
@@ -314,7 +318,7 @@
                 if ( SchemaConstants.POSTAL_ADDRESS_SYNTAX.equals( type.getSyntaxOid() ) )
                 {
                     // https://poi.apache.org/components/spreadsheet/quick-guide.html#NewLinesInCells
-                    value = Utils.decodePostalAddress( value, "\n" ); //$NON-NLS-1$
+                    value = DECODER.translate( value );
                     cell.setCellStyle( wrapStyle );
                 }
                 cell.setCellValue( value );
diff --git a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/utils/Utils.java b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/utils/Utils.java
index 604c8af..1247dd0 100644
--- a/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/utils/Utils.java
+++ b/plugins/ldapbrowser.core/src/main/java/org/apache/directory/studio/ldapbrowser/core/utils/Utils.java
@@ -35,6 +35,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.apache.commons.text.translate.CharSequenceTranslator;
+import org.apache.commons.text.translate.LookupTranslator;
 import org.apache.directory.api.ldap.model.name.Ava;
 import org.apache.directory.api.ldap.model.name.Dn;
 import org.apache.directory.api.ldap.model.name.Rdn;
@@ -673,30 +675,30 @@
      *                 / UTFMB
      * </pre>
      *
-     * @param input the encoded string
      * @param separator the separator to output between address lines
-     * @return the decoded string
+     * @return a translator object for decoding
      */
-    public static String decodePostalAddress( String input, String separator )
+    public static CharSequenceTranslator createPostalAddressDecoder( String separator )
     {
-        return input.replace( "$", separator ) //$NON-NLS-1$
-            .replace( "\\24", "$" ) //$NON-NLS-1$ //$NON-NLS-2$
-            .replace( "\\5C", "\\" ) //$NON-NLS-1$ //$NON-NLS-2$
-            .replace( "\\5c", "\\" ); //$NON-NLS-1$ //$NON-NLS-2$
+        return new LookupTranslator( Map.of(
+            "$", separator, //$NON-NLS-1$
+            "\\24", "$", //$NON-NLS-1$ //$NON-NLS-2$
+            "\\5C", "\\", //$NON-NLS-1$ //$NON-NLS-2$
+            "\\5c", "\\" ) ); //$NON-NLS-1$ //$NON-NLS-2$
     }
 
 
     /**
      * Encodes the RFC 4517 Postal Address syntax.
      *
-     * @param input the string to encode
      * @param separator the separator used between address lines
-     * @return the encoded string
+     * @return a translator object for encoding
      */
-    public static String encodePostalAddress( String input, String separator )
+    public static CharSequenceTranslator createPostalAddressEncoder( String separator )
     {
-        return input.replace( "\\", "\\5C" ) //$NON-NLS-1$ //$NON-NLS-2$
-            .replace( "$", "\\24" ) //$NON-NLS-1$ //$NON-NLS-2$
-            .replace( separator, "$" ); //$NON-NLS-1$
+        return new LookupTranslator( Map.of(
+            "\\", "\\5C", //$NON-NLS-1$ //$NON-NLS-2$
+            "$", "\\24", //$NON-NLS-1$ //$NON-NLS-2$
+            separator, "$" ) ); //$NON-NLS-1$
     }
 }
diff --git a/plugins/ldapbrowser.core/src/test/java/org/apache/directory/studio/ldapbrowser/core/utils/UtilsTest.java b/plugins/ldapbrowser.core/src/test/java/org/apache/directory/studio/ldapbrowser/core/utils/UtilsTest.java
index b99651f..0f88cbc 100644
--- a/plugins/ldapbrowser.core/src/test/java/org/apache/directory/studio/ldapbrowser/core/utils/UtilsTest.java
+++ b/plugins/ldapbrowser.core/src/test/java/org/apache/directory/studio/ldapbrowser/core/utils/UtilsTest.java
@@ -21,6 +21,7 @@
 
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.apache.commons.text.translate.CharSequenceTranslator;
 import org.junit.jupiter.api.Test;
 
 
@@ -29,36 +30,42 @@
     @Test
     public void testPostalAddressTrivial()
     {
-        assertEquals( "abc", Utils.decodePostalAddress( "abc", "!" ) );
-        assertEquals( "abc", Utils.encodePostalAddress( "abc", "!" ) );
+        assertEquals( "abc", Utils.createPostalAddressDecoder( "!" ).translate( "abc" ) );
+        assertEquals( "abc", Utils.createPostalAddressEncoder( "!" ).translate( "abc" ) );
     }
 
 
     @Test
     public void testPostalAddressEscaped()
     {
-        assertEquals( "!", Utils.decodePostalAddress( "$", "!" ) );
-        assertEquals( "$", Utils.decodePostalAddress( "\\24", "!" ) );
-        assertEquals( "\\", Utils.decodePostalAddress( "\\5C", "!" ) );
-        assertEquals( "\\", Utils.decodePostalAddress( "\\5c", "!" ) );
+        CharSequenceTranslator decoder = Utils.createPostalAddressDecoder( "!" );
+        assertEquals( "!", decoder.translate( "$" ) );
+        assertEquals( "$", decoder.translate( "\\24" ) );
+        assertEquals( "\\", decoder.translate( "\\5C" ) );
+        assertEquals( "\\", decoder.translate( "\\5c" ) );
+        assertEquals( "\\5C", decoder.translate( "\\5c5C" ) );
+        assertEquals( "\\5c", decoder.translate( "\\5C5c" ) );
 
-        assertEquals( "$", Utils.encodePostalAddress( "!", "!" ) );
-        assertEquals( "\\24", Utils.encodePostalAddress( "$", "!" ) );
-        assertEquals( "\\5C", Utils.encodePostalAddress( "\\", "!" ) );
+        CharSequenceTranslator encoder = Utils.createPostalAddressEncoder( "!" );
+        assertEquals( "$", encoder.translate( "!" ) );
+        assertEquals( "\\24", encoder.translate( "$" ) );
+        assertEquals( "\\5C", encoder.translate( "\\" ) );
     }
 
 
     @Test
     public void testPostalAddressRfcExamples()
     {
+        CharSequenceTranslator decoder = Utils.createPostalAddressDecoder( "\n" );
         assertEquals( "1234 Main St.\nAnytown, CA 12345\nUSA",
-            Utils.decodePostalAddress( "1234 Main St.$Anytown, CA 12345$USA", "\n" ) );
+            decoder.translate( "1234 Main St.$Anytown, CA 12345$USA" ) );
         assertEquals( "$1,000,000 Sweepstakes\nPO Box 1000000\nAnytown, CA 12345\nUSA",
-            Utils.decodePostalAddress( "\\241,000,000 Sweepstakes$PO Box 1000000$Anytown, CA 12345$USA", "\n" ) );
+            decoder.translate( "\\241,000,000 Sweepstakes$PO Box 1000000$Anytown, CA 12345$USA" ) );
 
+        CharSequenceTranslator encoder = Utils.createPostalAddressEncoder( "\n" );
         assertEquals( "1234 Main St.$Anytown, CA 12345$USA",
-            Utils.encodePostalAddress( "1234 Main St.\nAnytown, CA 12345\nUSA", "\n" ) );
+            encoder.translate( "1234 Main St.\nAnytown, CA 12345\nUSA" ) );
         assertEquals( "\\241,000,000 Sweepstakes$PO Box 1000000$Anytown, CA 12345$USA",
-            Utils.encodePostalAddress( "$1,000,000 Sweepstakes\nPO Box 1000000\nAnytown, CA 12345\nUSA", "\n" ) );
+            encoder.translate( "$1,000,000 Sweepstakes\nPO Box 1000000\nAnytown, CA 12345\nUSA" ) );
     }
 }
diff --git a/plugins/valueeditors/pom-first.xml b/plugins/valueeditors/pom-first.xml
index 95338de..f4e60c4 100644
--- a/plugins/valueeditors/pom-first.xml
+++ b/plugins/valueeditors/pom-first.xml
@@ -64,10 +64,12 @@
  org.apache.directory.studio.valueeditors.uuid</Export-Package>
  
             <Import-Package>org.apache.commons.codec,
- org.apache.commons.codec.binary
+ org.apache.commons.codec.binary,
+ org.apache.commons.text
             </Import-Package>
             
             <Require-Bundle>
+ org.apache.commons.commons-text,
  org.apache.directory.api.ldap.model;bundle-version="${org.apache.directory.api.bundleversion}",
  org.apache.directory.api.util;bundle-version="${org.apache.directory.api.bundleversion}",
  org.apache.directory.studio.common.ui,
diff --git a/plugins/valueeditors/src/main/java/org/apache/directory/studio/valueeditors/address/AddressDialog.java b/plugins/valueeditors/src/main/java/org/apache/directory/studio/valueeditors/address/AddressDialog.java
index d025204..5d38647 100644
--- a/plugins/valueeditors/src/main/java/org/apache/directory/studio/valueeditors/address/AddressDialog.java
+++ b/plugins/valueeditors/src/main/java/org/apache/directory/studio/valueeditors/address/AddressDialog.java
@@ -21,6 +21,7 @@
 package org.apache.directory.studio.valueeditors.address;
 
 
+import org.apache.commons.text.translate.CharSequenceTranslator;
 import org.apache.directory.studio.ldapbrowser.core.BrowserCoreConstants;
 import org.apache.directory.studio.ldapbrowser.core.utils.Utils;
 import org.apache.directory.studio.valueeditors.ValueEditorsActivator;
@@ -52,6 +53,11 @@
     /** The text widget. */
     private Text text;
 
+    /** The postal address decoder. */
+    private CharSequenceTranslator decoder;
+
+    /** The postal address encoder. */
+    private CharSequenceTranslator encoder;
 
     /**
      * Creates a new instance of AddressDialog.
@@ -65,6 +71,8 @@
         super.setShellStyle( super.getShellStyle() | SWT.RESIZE );
         this.initialAddress = initialAddress;
         this.returnAddress = null;
+        this.decoder = Utils.createPostalAddressDecoder( BrowserCoreConstants.LINE_SEPARATOR );
+        this.encoder = Utils.createPostalAddressEncoder( BrowserCoreConstants.LINE_SEPARATOR );
     }
 
 
@@ -94,7 +102,7 @@
      */
     protected void okPressed()
     {
-        returnAddress = Utils.encodePostalAddress( text.getText(), BrowserCoreConstants.LINE_SEPARATOR );
+        returnAddress = encoder.translate( text.getText() );
         super.okPressed();
     }
 
@@ -111,7 +119,7 @@
 
         // text widget
         text = new Text( composite, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL );
-        text.setText( Utils.decodePostalAddress( initialAddress, BrowserCoreConstants.LINE_SEPARATOR ) );
+        text.setText( decoder.translate( initialAddress ) );
         // GridData gd = new GridData(GridData.GRAB_HORIZONTAL |
         // GridData.HORIZONTAL_ALIGN_FILL);
         gd = new GridData( GridData.FILL_BOTH );
diff --git a/plugins/valueeditors/src/main/java/org/apache/directory/studio/valueeditors/address/AddressValueEditor.java b/plugins/valueeditors/src/main/java/org/apache/directory/studio/valueeditors/address/AddressValueEditor.java
index 2eac89b..2b54d57 100644
--- a/plugins/valueeditors/src/main/java/org/apache/directory/studio/valueeditors/address/AddressValueEditor.java
+++ b/plugins/valueeditors/src/main/java/org/apache/directory/studio/valueeditors/address/AddressValueEditor.java
@@ -21,6 +21,7 @@
 package org.apache.directory.studio.valueeditors.address;
 
 
+import org.apache.commons.text.translate.CharSequenceTranslator;
 import org.apache.directory.studio.ldapbrowser.core.model.IValue;
 import org.apache.directory.studio.ldapbrowser.core.utils.Utils;
 import org.apache.directory.studio.valueeditors.AbstractDialogStringValueEditor;
@@ -37,6 +38,10 @@
  */
 public class AddressValueEditor extends AbstractDialogStringValueEditor
 {
+    /** The postal address decoder. */
+    private CharSequenceTranslator decoder = Utils.createPostalAddressDecoder( ", " ); //$NON-NLS-1$
+
+
     /**
      * {@inheritDoc}
      * 
@@ -73,7 +78,7 @@
 
         if ( !showRawValues() )
         {
-            displayValue = Utils.decodePostalAddress( displayValue, ", " ); //$NON-NLS-1$
+            displayValue = decoder.translate( displayValue );
         }
 
         return displayValue;
diff --git a/pom.xml b/pom.xml
index 6f5fd4e..07e58ad 100644
--- a/pom.xml
+++ b/pom.xml
@@ -94,6 +94,8 @@
     <org.apache.commons.lang3.bundleversion>3.12.0</org.apache.commons.lang3.bundleversion>
     <org.apache.commons.pool.version>2.9.0</org.apache.commons.pool.version>
     <org.apache.commons.pool.bundleversion>2.9.0</org.apache.commons.pool.bundleversion>
+    <org.apache.commons.text.version>1.9</org.apache.commons.text.version>
+    <org.apache.commons.text.bundleversion>1.9</org.apache.commons.text.bundleversion>
     <org.apache.directory.api.version>2.1.0</org.apache.directory.api.version>
     <org.apache.directory.api.bundleversion>2.1.0</org.apache.directory.api.bundleversion>
     <org.apache.directory.server.version>2.0.0.AM26</org.apache.directory.server.version>
@@ -137,8 +139,8 @@
         <groupId>org.eclipse.tycho</groupId>
         <artifactId>tycho-compiler-plugin</artifactId>
         <configuration>
-          <source>1.8</source>
-          <target>1.8</target>
+          <source>11</source>
+          <target>11</target>
         </configuration>
       </plugin>