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

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new b2cc74dd77 Fix an almost never-ending loop when loging a warning with 
a message too long for fitting on a line, and when that line has no place 
(space, dash, etc.) where to split.
b2cc74dd77 is described below

commit b2cc74dd77628ce6d0cf25929b602390a071281c
Author: Martin Desruisseaux <martin.desruisse...@geomatys.com>
AuthorDate: Fri Jan 26 20:21:33 2024 +0100

    Fix an almost never-ending loop when loging a warning with a message too 
long for fitting on a line,
    and when that line has no place (space, dash, etc.) where to split.
---
 .../main/org/apache/sis/console/SIS.java           |  14 ++-
 .../apache/sis/xml/util/ExternalLinkHandler.java   |   1 -
 .../apache/sis/storage/event/StoreListeners.java   |   1 +
 .../main/org/apache/sis/io/LineAppender.java       | 118 ++++++++++++++-------
 .../apache/sis/util/logging/MonolineFormatter.java |   2 +-
 .../test/org/apache/sis/io/AppenderTestCase.java   |   4 +-
 .../test/org/apache/sis/io/EchoAppendable.java     |   2 +-
 .../test/org/apache/sis/io/LeftMarginTest.java     |   2 +-
 .../test/org/apache/sis/io/LineAppenderTest.java   |   4 +-
 .../test/org/apache/sis/io/TableAppenderTest.java  |   2 +-
 .../org/apache/sis/io/TabulationExpansionTest.java |   4 +-
 .../test/org/apache/sis/io/WordWrapTest.java       |  33 ++++--
 .../sis/io/WordWrapWithLineSeparatorTest.java      |   2 +-
 13 files changed, 133 insertions(+), 56 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java 
b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
index 3b6a099470..26fb629a57 100644
--- a/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
+++ b/endorsed/src/org.apache.sis.console/main/org/apache/sis/console/SIS.java
@@ -626,7 +626,7 @@ public final class SIS extends Static {
         Transform() {super("transform");}
 
         /**
-         * Sets Coordinate Reference System of input data.
+         * Sets the Coordinate Reference System of input data.
          *
          * @param  value  the EPSG code, WKT or file from which to get the CRS.
          * @return a new builder or {@code this}, for method call chaining.
@@ -636,7 +636,7 @@ public final class SIS extends Static {
         }
 
         /**
-         * Sets Coordinate Reference System of output data.
+         * Sets the Coordinate Reference System of output data.
          *
          * @param  value  the EPSG code, WKT or file from which to get the CRS.
          * @return a new builder or {@code this}, for method call chaining.
@@ -645,6 +645,16 @@ public final class SIS extends Static {
             return set(Option.TARGET_CRS, value);
         }
 
+        /**
+         * Sets the Coordinate Operation to use.
+         *
+         * @param  value  the EPSG code, WKT or file from which to get the 
coordinate operation.
+         * @return a new builder or {@code this}, for method call chaining.
+         */
+        public Transform operation(Object value) {
+            return set(Option.OPERATION, value);
+        }
+
         /**
          * Sets the locale to use for the command output.
          *
diff --git 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExternalLinkHandler.java
 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExternalLinkHandler.java
index 6bb7082bc7..9dbbdc9a22 100644
--- 
a/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExternalLinkHandler.java
+++ 
b/endorsed/src/org.apache.sis.metadata/main/org/apache/sis/xml/util/ExternalLinkHandler.java
@@ -185,7 +185,6 @@ public class ExternalLinkHandler {
      * @param  cause  the exception that occurred while trying to process the 
document.
      */
     public static void warningOccured(final Object href, final Exception 
cause) {
-        Context.warningOccured(Context.current(), ReferenceResolver.class, 
"resolve", cause, true);
         Context.warningOccured(Context.current(), Level.WARNING, 
ReferenceResolver.class, "resolve",
                                cause, Errors.class, Errors.Keys.CanNotRead_1, 
href);
     }
diff --git 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/event/StoreListeners.java
 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/event/StoreListeners.java
index a648272f76..c4f7bf04f7 100644
--- 
a/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/event/StoreListeners.java
+++ 
b/endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/event/StoreListeners.java
@@ -626,6 +626,7 @@ public class StoreListeners implements Localized {
     public <E extends StoreEvent> boolean fire(final Class<E> eventType, final 
E event) {
         ArgumentChecks.ensureNonNull("event", event);
         ArgumentChecks.ensureNonNull("eventType", eventType);
+        @SuppressWarnings("LocalVariableHidesMemberVariable")
         final Set<Class<? extends StoreEvent>> permittedEventTypes = 
this.permittedEventTypes;
         if (permittedEventTypes != null && 
!permittedEventTypes.contains(eventType)) {
             throw illegalEventType(eventType);
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/LineAppender.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/LineAppender.java
index 899de1153e..a42e99072f 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/LineAppender.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/io/LineAppender.java
@@ -379,7 +379,6 @@ public class LineAppender extends Appender implements 
Flushable {
      *
      * @throws IOException if an I/O error occurs.
      */
-    @SuppressWarnings("fallthrough")
     private void write(final int c) throws IOException {
         /*
          * If the character to write is a EOL sequence, then:
@@ -410,10 +409,10 @@ public class LineAppender extends Appender implements 
Flushable {
         /*
          * If the character to write is a whitespace, then write any pending 
characters from
          * the buffer to the underlying appendable since we know that those 
characters didn't
-         * exceeded the line length limit.
+         * exceed the line length limit.
          *
          * We use `Character.isWhitespace(…)` instead of 
`Character.isSpaceChar(…)` because
-         * the former returns `true` tabulations (which we want), and returns 
`false`
+         * the former returns `true` for tabulations (which we want), and 
returns `false`
          * for non-breaking spaces (which we also want).
          */
         if (Character.isWhitespace(c)) {
@@ -460,48 +459,95 @@ public class LineAppender extends Appender implements 
Flushable {
         }
         /*
          * The remaining of this method is executed only if we exceeded the 
maximal line length.
-         * First, search for the hyphen character, if any. If we find one and 
if it is preceeded
-         * by a letter, split there. The "letter before" condition is a way to 
avoid to split at
-         * the minus sign of negative numbers like "-99", assuming that the 
minus sign is preceeded
-         * by a space. We cannot look at the character after since we may not 
know it yet.
+         * First, search for a dash character (hyphen) for splitting the line 
after it. If we do
+         * not find a dash character, as a fallback split on any non-letter or 
digit characters
+         * except the punctuation starts.
          */
-        if (++codePointCount > maximalLineLength) {
-searchHyp:  for (int i=buffer.length(); i>0;) {
-                final int b = buffer.codePointBefore(i);
-                final int n = Character.charCount(b);
-                switch (b) {
-                    case '-': {
-                        if (i>=n && 
!Character.isLetter(buffer.codePointBefore(i-n))) {
-                            break;                      // Continue searching 
previous characters.
+        if (++codePointCount < maximalLineLength) {
+            return;
+        }
+        int splitAt = buffer.length();          // Where to separate the line 
as two lines.
+        int fallback = splitAt;                 // Fallback to use if we could 
not find a value for `splitAt`.
+        boolean hasFallback = false;            // Whether the `fallback` 
value has been defined.
+split:  for (;;) {
+            if (splitAt <= 0) {
+                splitAt = fallback;
+                break;
+            }
+            int b = buffer.codePointBefore(splitAt);
+            int n = Character.charCount(b);
+            switch (Character.getType(b)) {
+                case Character.UPPERCASE_LETTER:
+                case Character.LOWERCASE_LETTER:
+                case Character.TITLECASE_LETTER:
+                case Character.MODIFIER_LETTER:
+                case Character.OTHER_LETTER:
+                case Character.DECIMAL_DIGIT_NUMBER:
+                case Character.INITIAL_QUOTE_PUNCTUATION:
+                case Character.START_PUNCTUATION: break;            // Do 
nothing (search another character).
+                case Character.PARAGRAPH_SEPARATOR:
+                case Character.SPACE_SEPARATOR:
+                case Character.LINE_SEPARATOR:
+                case Character.CONTROL: {
+                    /*
+                     * Split the line before a space (except no-break space) 
and discard trailing spaces.
+                     * The `isWhitespace(b)` check is necessary for excluding 
the no-break spaces.
+                     */
+                    final int end = splitAt;
+                    while (Character.isWhitespace(b)) {
+                        if ((splitAt -= n) <= 0) break;
+                        b = buffer.codePointBefore(splitAt);
+                        n = Character.charCount(b);
+                    }
+                    if (splitAt == end) break;                      // 
No-break space. Search another character.
+                    buffer.delete(splitAt, end);
+                    break split;                                    // Split 
here (before the space character).
+                }
+                /*
+                 * Split the line after a dash character.
+                 * The "letter before" condition is a way to avoid splitting 
at the minus sign
+                 * of negative numbers, assuming that the minus sign is 
preceeded by a space.
+                 * We cannot look at the character after because it may not be 
in the buffer yet.
+                 */
+                case Character.DASH_PUNCTUATION: {
+                    if (b == '-') {
+                        b = splitAt - n;
+                        if (b > 0 && 
!Character.isLetter(buffer.codePointBefore(b))) {
+                            break;      // Continue the search in previous 
characters.
                         }
-                        // fall through
                     }
-                    case Characters.HYPHEN:
-                    case Characters.SOFT_HYPHEN: {
-                        transfer(i);
-                        break searchHyp;
+                    break split;        // Split here (after the dash 
character).
+                }
+                /*
+                 * Soft hyphen are not in the dash category, so they need to 
be checked here.
+                 * Replace soft-hyphen by ordinary (visible) hyphen since the 
hyphen is used.
+                 */
+                case Character.FORMAT: {
+                    if (b == Characters.SOFT_HYPHEN) {
+                        buffer.setCharAt(splitAt - n, Characters.HYPHEN);
+                        break split;    // Split here (after the dash 
character).
                     }
+                    break;              // Do nothing (search another 
character).
                 }
-                i -= n;
-            }
-            /*
-             * At this point, all the remaining content of the buffer must 
move on the next line.
-             * Skip the leading whitespaces on the new line.
-             */
-            writeLineSeparator();
-            final int length = buffer.length();
-            for (int i=0; i<length;) {
-                final int s = buffer.codePointAt(i);
-                if (!Character.isWhitespace(s)) {
-                    buffer.delete(0, i);
+                /*
+                 * All other categories (e.g. punctuations) may be used as a 
split point
+                 * if no better location is found.
+                 */
+                default: {
+                    if (!hasFallback && b != '<') {
+                        hasFallback = true;
+                        fallback = splitAt;
+                    }
                     break;
                 }
-                i += Character.charCount(s);
             }
-            printableLength = buffer.length();
-            codePointCount  = buffer.codePointCount(0, printableLength);
-            onLineBegin(true);
+            splitAt -= n;
         }
+        transfer(splitAt);
+        writeLineSeparator();
+        printableLength = buffer.length();          // Remaining characters 
will be on next line.
+        codePointCount  = buffer.codePointCount(0, printableLength);
+        onLineBegin(true);
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/MonolineFormatter.java
 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/MonolineFormatter.java
index 687d6dcc9b..691986a1d0 100644
--- 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/MonolineFormatter.java
+++ 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/util/logging/MonolineFormatter.java
@@ -792,7 +792,7 @@ loop:   for (int i=0; ; i++) {
              * Now for the message part, we need to use the LineAppender in 
order to replace EOL
              * and tabulations.
              */
-            writer.setMaximalLineLength(maximalLength);
+            writer.setMaximalLineLength(Math.max(maximalLength - margin - 3, 
10));
             writer.setCurrentLineLength(X364.lengthOfPlain(buffer, 0, 
buffer.length()));
             try {
                 if (message != null) {
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/AppenderTestCase.java 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/AppenderTestCase.java
index eb868a0973..0648563292 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/AppenderTestCase.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/AppenderTestCase.java
@@ -20,7 +20,7 @@ import java.io.IOException;
 
 // Test dependencies
 import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOnMethod;
 import static org.apache.sis.test.Assertions.assertMultilinesEquals;
@@ -75,7 +75,7 @@ public abstract class AppenderTestCase extends TestCase {
         IO.flush(appender);
         final String actual = buffer.toString();
         assertMultilinesEquals("Ignoring line separators.", expected, actual);
-        assertEquals          ("Checking line separators.", expected, actual);
+        assertEquals(expected, actual, "Checking line separators.");
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/EchoAppendable.java 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/EchoAppendable.java
index fd7bd7efca..aa7910822d 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/EchoAppendable.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/EchoAppendable.java
@@ -19,7 +19,7 @@ package org.apache.sis.io;
 import java.io.IOException;
 
 // Test dependencies
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 
 
 /**
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/LeftMarginTest.java 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/LeftMarginTest.java
index fbe2cb69c5..720bf23727 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/LeftMarginTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/LeftMarginTest.java
@@ -20,7 +20,7 @@ import java.io.IOException;
 
 // Test dependencies
 import org.junit.Before;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.DependsOn;
 
 
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/LineAppenderTest.java 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/LineAppenderTest.java
index 865b03b96c..4d79776c26 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/LineAppenderTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/LineAppenderTest.java
@@ -21,7 +21,7 @@ import java.io.IOException;
 // Test dependencies
 import org.junit.Before;
 import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.DependsOn;
 
 
@@ -62,7 +62,7 @@ public class LineAppenderTest extends AppenderTestCase {
     void run(final String lineSeparator) throws IOException {
         final Appendable f = appender;
         if (f instanceof LineAppender) {
-            assertEquals("getLineSeparator", " ", ((LineAppender) 
f).getLineSeparator());
+            assertEquals(" ", ((LineAppender) f).getLineSeparator());
         }
         assertSame(f, f.append("Le vrai" + lineSeparator + "policitien, "));
         assertSame(f, f.append("c'est celui\r\nqui\r")); // Line separator 
broken on two method calls.
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/TableAppenderTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/TableAppenderTest.java
index 967d99957c..78028465a7 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/TableAppenderTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/TableAppenderTest.java
@@ -20,7 +20,7 @@ import java.io.IOException;
 
 // Test dependencies
 import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.DependsOn;
 
 
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/TabulationExpansionTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/TabulationExpansionTest.java
index 6fbc487016..ba661dde6b 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/TabulationExpansionTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/TabulationExpansionTest.java
@@ -20,7 +20,7 @@ import java.io.IOException;
 
 // Test dependencies
 import org.junit.Before;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.DependsOn;
 
 
@@ -58,7 +58,7 @@ public final class TabulationExpansionTest extends 
LineAppenderTest {
     void run(final String lineSeparator) throws IOException {
         final Appendable f = appender;
         if (f instanceof LineAppender) {
-            assertEquals("getTabWidth", 8, ((LineAppender) 
f).getTabulationWidth());
+            assertEquals(8, ((LineAppender) f).getTabulationWidth());
         }
         assertSame(f, f.append("12\t8"   + lineSeparator));
         assertSame(f, f.append("1234\t8" + lineSeparator));
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/WordWrapTest.java 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/WordWrapTest.java
index de8c5ece7c..391940dc7c 100644
--- a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/WordWrapTest.java
+++ b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/WordWrapTest.java
@@ -17,12 +17,13 @@
 package org.apache.sis.io;
 
 import java.io.IOException;
+import org.apache.sis.util.Characters;
 import org.apache.sis.util.internal.X364;
-import static org.apache.sis.util.Characters.SOFT_HYPHEN;
 
 // Test dependencies
+import org.junit.Test;
 import org.junit.Before;
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
 import org.apache.sis.test.DependsOn;
 
 
@@ -35,6 +36,11 @@ import org.apache.sis.test.DependsOn;
  */
 @DependsOn(LineAppenderTest.class)
 public class WordWrapTest extends LineAppenderTest {
+    /**
+     * The line length used in the tests.
+     */
+    static final int LINE_LENGTH = 11;
+
     /**
      * Creates a new test case.
      */
@@ -47,7 +53,7 @@ public class WordWrapTest extends LineAppenderTest {
     @Before
     @Override
     public void createLineAppender() {
-        appender = new LineAppender(appender, 10, false);
+        appender = new LineAppender(appender, 11, false);
     }
 
     /**
@@ -70,12 +76,12 @@ public class WordWrapTest extends LineAppenderTest {
     void run(final String lineSeparator) throws IOException {
         final Appendable f = appender;
         if (f instanceof LineAppender) {
-            assertEquals("getMaximalLineLength", 10, ((LineAppender) 
f).getMaximalLineLength());
+            assertEquals(LINE_LENGTH, ((LineAppender) 
f).getMaximalLineLength());
         }
         final String BLUE    = X364.FOREGROUND_BLUE   .sequence();
         final String DEFAULT = X364.FOREGROUND_DEFAULT.sequence();
         assertSame(f, f.append("Ah! comme la " + BLUE + "neige" + DEFAULT + " 
a neigé!" + lineSeparator));
-        assertSame(f, f.append("Ma vitre est un jar" + SOFT_HYPHEN + "din de 
givre."    + lineSeparator));
+        assertSame(f, f.append("Ma vitre est un jar" + Characters.SOFT_HYPHEN 
+ "din de givre." + lineSeparator));
         /*
          * If our test case is using the wrapper which will send the data once 
character at time,
          * our LineAppender implementation will not be able to detect the line 
separator and
@@ -93,8 +99,23 @@ public class WordWrapTest extends LineAppenderTest {
                          + "la " + BLUE + "neige" + DEFAULT + " a" + 
insertedLineSeparator
                          + "neigé!"                                + 
expectedLineSeparator
                          + "Ma vitre"                              + 
insertedLineSeparator
-                         + "est un jar" + SOFT_HYPHEN              + 
insertedLineSeparator
+                         + "est un jar" + Characters.HYPHEN        + 
insertedLineSeparator
                          + "din de"                                + 
insertedLineSeparator
                          + "givre."                                + 
expectedLineSeparator);
     }
+
+    /**
+     * Test splitting a long lines into shorter lines.
+     *
+     * @throws IOException should never happen, since we are writing in a 
{@link StringBuilder}.
+     */
+    @Test
+    public void testLineSplit() throws IOException {
+        final LineAppender f = (LineAppender) appender;
+        final String ls = expectedLineSeparator("\n");
+        f.setLineSeparator("\n");
+        f.setMaximalLineLength(LINE_LENGTH);
+        f.append("bar foo-biz bla\nThisLineIsTooLong");
+        assertOutputEquals("bar foo-" + ls + "biz bla" + ls + "ThisLineIsT" + 
ls + "ooLong");
+    }
 }
diff --git 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/WordWrapWithLineSeparatorTest.java
 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/WordWrapWithLineSeparatorTest.java
index 39198cf619..6e0c8a76f2 100644
--- 
a/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/WordWrapWithLineSeparatorTest.java
+++ 
b/endorsed/src/org.apache.sis.util/test/org/apache/sis/io/WordWrapWithLineSeparatorTest.java
@@ -43,7 +43,7 @@ public final class WordWrapWithLineSeparatorTest extends 
WordWrapTest {
     @Before
     @Override
     public void createLineAppender() {
-        appender = new LineAppender(new LineAppender(appender, "\r", false), 
10, false);
+        appender = new LineAppender(new LineAppender(appender, "\r", false), 
WordWrapTest.LINE_LENGTH, false);
     }
 
     /**

Reply via email to