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); } /**