You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2018/07/06 22:41:49 UTC
[commons-geometry] 02/08: GEOMETRY-3: adding generic coordinate
format and parsing classes
This is an automated email from the ASF dual-hosted git repository.
erans pushed a commit to branch GEOMETRY-3__TBR
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git
commit f255ccce7c5736f2eb88c91ef57a4483adede29c
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Sat Jun 2 00:54:34 2018 -0400
GEOMETRY-3: adding generic coordinate format and parsing classes
---
.../core/util/AbstractCoordinateParser.java | 239 +++++++++++
.../commons/geometry/core/util/Coordinates.java | 65 +++
.../geometry/core/util/SimpleCoordinateFormat.java | 177 ++++++++
.../core/util/SimpleCoordinateFormatTest.java | 453 +++++++++++++++++++++
4 files changed, 934 insertions(+)
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/AbstractCoordinateParser.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/AbstractCoordinateParser.java
new file mode 100644
index 0000000..ec906bc
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/AbstractCoordinateParser.java
@@ -0,0 +1,239 @@
+/*
+ * 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.commons.geometry.core.util;
+
+import java.text.ParsePosition;
+
+/** Abstract class providing basic parsing functionality for reading coordinate tuples
+ * from strings.
+ */
+public abstract class AbstractCoordinateParser {
+
+ /** String separating coordinate values */
+ private final String separator;
+
+ /** String used to signal the start of a coordinate tuple; may be null */
+ private final String prefix;
+
+ /** String used to signal the end of a coordinate tuple; may be null */
+ private final String suffix;
+
+ /** Simple constructor
+ * @param separator String used to separate coordinate values; must not be null.
+ * @param prefix String used to signal the start of a coordinate tuple; if null, no
+ * string is expected at the start of the tuple
+ * @param suffix String used to signal the end of a coordinate tuple; if null, no
+ * string is expected at the end of the tuple
+ */
+ protected AbstractCoordinateParser(String separator, String prefix, String suffix) {
+ this.separator = separator;
+ this.prefix = prefix;
+ this.suffix = suffix;
+ }
+
+ /** Returns the string used to separate coordinate values.
+ * @return the coordinate value separator string
+ */
+ public String getSeparator() {
+ return separator;
+ }
+
+ /** Returns the string used to signal the start of a coordinate tuple. This value may be null.
+ * @return the string used to begin each coordinate tuple or null
+ */
+ public String getPrefix() {
+ return prefix;
+ }
+
+ /** Returns the string used to signal the end of a coordinate tuple. This value may be null.
+ * @return the string used to end each coordinate tuple or null
+ */
+ public String getSuffix() {
+ return suffix;
+ }
+
+ /** Reads the configured prefix from the current position in the given string, ignoring any preceding
+ * whitespace, and advances the parsing position past the prefix sequence. An exception is thrown if the
+ * prefix is not found. Does nothing if the prefix is null.
+ * @param str the string being parsed
+ * @param pos the current parsing position
+ * @throws IllegalArgumentException if the configured prefix is not null and is not found at the current
+ * parsing position, ignoring preceding whitespace
+ */
+ protected void readPrefix(String str, ParsePosition pos) throws IllegalArgumentException {
+ if (prefix != null) {
+ consumeWhitespace(str, pos);
+ readSequence(str, prefix, pos);
+ }
+ }
+
+ /** Reads and returns a coordinate value from the current position in the given string. An exception is thrown if a
+ * valid number is not found. The parsing position is advanced past the parsed number and any trailing separator.
+ * @param str the string being parsed
+ * @param pos the current parsing position
+ * @return the coordinate value
+ * @throws IllegalArgumentException if the configured prefix is not null and is not found at the current
+ * parsing position, ignoring preceding whitespace
+ */
+ protected double readCoordinateValue(String str, ParsePosition pos) throws IllegalArgumentException {
+ final int startIdx = pos.getIndex();
+
+ int endIdx = str.indexOf(separator, startIdx);
+ if (endIdx < 0) {
+ if (suffix != null) {
+ endIdx = str.indexOf(suffix, startIdx);
+ }
+
+ if (endIdx < 0) {
+ endIdx = str.length();
+ }
+ }
+
+ String substr = str.substring(startIdx, endIdx);
+ try {
+ double value = Double.parseDouble(substr);
+
+ // advance the position and move past any terminating separator
+ pos.setIndex(endIdx);
+ matchSequence(str, separator, pos);
+
+ return value;
+ }
+ catch (NumberFormatException exc) {
+ throw new CoordinateParseException("Failed to parse number from string at index " + startIdx + ": " + substr, exc);
+ }
+ }
+
+ /** Reads the configured suffix from the current position in the given string, ignoring any preceding
+ * whitespace, and advances the parsing position past the suffix sequence. An exception is thrown if the
+ * suffix is not found. Does nothing if the suffix is null.
+ * @param str the string being parsed
+ * @param pos the current parsing position
+ * @throws IllegalArgumentException if the configured suffix is not null and is not found at the current
+ * parsing position, ignoring preceding whitespace
+ */
+ protected void readSuffix(String str, ParsePosition pos) throws IllegalArgumentException {
+ if (suffix != null) {
+ consumeWhitespace(str, pos);
+ readSequence(str, suffix, pos);
+ }
+ }
+
+ /** Ends a parse operation by ensuring that all non-whitespace characters in the string have been parsed. An exception
+ * is thrown if extra content is found.
+ * @param str the string being parsed
+ * @param pos the current parsing position
+ * @throws IllegalArgumentException if extra non-whitespace content is found past the current parsing position
+ */
+ protected void endParse(String str, ParsePosition pos) throws IllegalArgumentException {
+ consumeWhitespace(str, pos);
+ if (pos.getIndex() != str.length()) {
+ throw new CoordinateParseException("Failed to parse string: unexpected content at index " + pos.getIndex());
+ }
+ }
+
+ /** Advances {@code pos} past any whitespace characters in {@code str},
+ * starting at the current parse position index.
+ * @param str the input string
+ * @param pos the current parse position
+ */
+ protected void consumeWhitespace(String str, ParsePosition pos) {
+ int idx = pos.getIndex();
+ final int len = str.length();
+
+ for (; idx<len; ++idx) {
+ if (!Character.isWhitespace(str.codePointAt(idx))) {
+ break;
+ }
+ }
+
+ pos.setIndex(idx);
+ }
+
+ /** Returns a boolean indicating whether or not the input string {@code str}
+ * contains the string {@code seq} at the given parse index. If the match succeeds,
+ * the index of {@code pos} is moved to the first character after the match. If
+ * the match does not succeed, the parse position is left unchanged.
+ * @param str the string to match against
+ * @param seq the sequence to look for in {@code str}
+ * @param pos the parse position indicating the index in {@code str}
+ * to attempt the match
+ * @return true if {@code str} contains exactly the same characters as {@code seq}
+ * at {@code pos}; otherwise, false
+ */
+ protected boolean matchSequence(String str, String seq, ParsePosition pos) {
+ final int idx = pos.getIndex();
+ final int inputLength = str.length();
+ final int seqLength = seq.length();
+
+ int i = idx;
+ int s = 0;
+ for (; i<inputLength && s<seqLength; ++i, ++s) {
+ if (str.codePointAt(i) != seq.codePointAt(s)) {
+ break;
+ }
+ }
+
+ if (i <= inputLength && s == seqLength) {
+ pos.setIndex(idx + seqLength);
+ return true;
+ }
+ return false;
+ }
+
+ /** Reads the string given by {@code seq} from the given position in {@code str}.
+ * Throws an IllegalArgumentException if the sequence is not found at that position.
+ * @param str the string to match against
+ * @param seq the sequence to look for in {@code str}
+ * @param pos the parse position indicating the index in {@code str}
+ * to attempt the match
+ * @throws IllegalArgumentException if {@code str} does not contain the characters from
+ * {@code seq} at position {@code pos}
+ */
+ protected void readSequence(String str, String seq, ParsePosition pos) throws IllegalArgumentException {
+ if (!matchSequence(str, seq, pos)) {
+ final int idx = pos.getIndex();
+ final String actualSeq = str.substring(idx, Math.min(str.length(), idx + seq.length()));
+
+ throw new CoordinateParseException("Failed to parse string: expected \"" + seq +
+ "\" but found \"" + actualSeq + "\" at index " + idx);
+ }
+ }
+
+ /** Exception class for errors occurring during coordinate parsing.
+ */
+ private static class CoordinateParseException extends IllegalArgumentException {
+
+ /** Serializable version identifier */
+ private static final long serialVersionUID = 1494716029613981959L;
+
+ /** Simple constructor.
+ * @param msg the exception message.
+ */
+ public CoordinateParseException(String msg) {
+ super(msg);
+ }
+
+ /** Simple constructor with cause.
+ * @param msg the exception message
+ * @param cause the exception root cause
+ */
+ public CoordinateParseException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/Coordinates.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/Coordinates.java
new file mode 100644
index 0000000..fcafd4f
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/Coordinates.java
@@ -0,0 +1,65 @@
+/*
+ * 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.commons.geometry.core.util;
+
+/** Utility class for working with coordinate tuples.
+ */
+public class Coordinates {
+
+ /** Interface for classes that create new instances of a type from a single coordinate value.
+ * @param <T> The type created by this factory.
+ */
+ public static interface Factory1D<T> {
+
+ /** Creates a new instance of type T from the given coordinate value.
+ * @param v the first coordinate value
+ * @return a new instance of type T
+ */
+ T create(double v);
+ }
+
+ /** Interface for classes that create new instances of a type from two coordinate values.
+ * @param <T> The type created by this factory.
+ */
+ public static interface Factory2D<T> {
+
+ /** Creates a new instance of type T from the given coordinate values.
+ * @param v1 the first coordinate value
+ * @param v2 the second coordinate value
+ * @return a new instance of type T
+ */
+ T create(double v1, double v2);
+ }
+
+ /** Interface for classes that create new instances of a type from three coordinate values.
+ * @param <T> The type created by this factory.
+ */
+ public static interface Factory3D<T> {
+
+ /** Creates a new instance of type T from the given coordinate values.
+ * @param v1 the first coordinate value
+ * @param v2 the second coordinate value
+ * @param v3 the third coordinate value
+ * @return a new instance of type T
+ */
+ T create(double v1, double v2, double v3);
+ }
+
+ /** Private constructor. */
+ private Coordinates() {
+ }
+}
diff --git a/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormat.java b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormat.java
new file mode 100644
index 0000000..fd66e45
--- /dev/null
+++ b/commons-geometry-core/src/main/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormat.java
@@ -0,0 +1,177 @@
+/*
+ * 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.commons.geometry.core.util;
+
+import java.text.ParsePosition;
+
+/** Class for performing simple formatting and parsing of coordinate tuples in common dimensions.
+ */
+public class SimpleCoordinateFormat extends AbstractCoordinateParser {
+
+ /** Default coordinate separator value */
+ private static final String DEFAULT_SEPARATOR = ",";
+
+ /** Space character */
+ private static final String SPACE = " ";
+
+ /** Creates a new format instance with the default separator value and the given
+ * tuple prefix and suffix.
+ * @param prefix coordinate tuple prefix; may be null
+ * @param suffix coordinate tuple suffix; may be null
+ */
+ public SimpleCoordinateFormat(String prefix, String suffix) {
+ this(DEFAULT_SEPARATOR, prefix, suffix);
+ }
+
+ /** Creates a new format instance with the given separator, prefix, and suffix.
+ * @param separator string separating coordinate values
+ * @param prefix coordinate tuple prefix; may be null
+ * @param suffix coordinate tuple suffix; may be null
+ */
+ public SimpleCoordinateFormat(String separator, String prefix, String suffix) {
+ super(separator, prefix, suffix);
+ }
+
+ /** Returns a 1D coordinate tuple string with the given value.
+ * @param v coordinate value
+ * @return 1D coordinate tuple string
+ */
+ public String format1D(double v) {
+ StringBuilder sb = new StringBuilder();
+
+ if (getPrefix() != null) {
+ sb.append(getPrefix());
+ }
+
+ sb.append(v);
+
+ if (getSuffix() != null) {
+ sb.append(getSuffix());
+ }
+
+ return sb.toString();
+ }
+
+ /** Returns a 2D coordinate tuple string with the given values.
+ * @param v1 first coordinate value
+ * @param v2 second coordinate value
+ * @return 2D coordinate tuple string
+ */
+ public String format2D(double v1, double v2) {
+ StringBuilder sb = new StringBuilder();
+
+ if (getPrefix() != null) {
+ sb.append(getPrefix());
+ }
+
+ sb.append(v1);
+ sb.append(getSeparator());
+ sb.append(SPACE);
+ sb.append(v2);
+
+ if (getSuffix() != null) {
+ sb.append(getSuffix());
+ }
+
+ return sb.toString();
+ }
+
+ /** Returns a 3D coordinate tuple string with the given values.
+ * @param v1 first coordinate value
+ * @param v2 second coordinate value
+ * @param v3 third coordinate value
+ * @return 3D coordinate tuple string
+ */
+ public String format3D(double v1, double v2, double v3) {
+ StringBuilder sb = new StringBuilder();
+
+ if (getPrefix() != null) {
+ sb.append(getPrefix());
+ }
+
+ sb.append(v1);
+ sb.append(getSeparator());
+ sb.append(SPACE);
+ sb.append(v2);
+ sb.append(getSeparator());
+ sb.append(SPACE);
+ sb.append(v3);
+
+ if (getSuffix() != null) {
+ sb.append(getSuffix());
+ }
+
+ return sb.toString();
+ }
+
+ /** Parses the given string as a 1D coordinate tuple and passes the coordinate value to the
+ * given factory. The object created by the factory is returned.
+ * @param str the string to be parsed
+ * @param factory object that will be passed the parsed coordinate value
+ * @return object created by {@code factory}
+ * @throws IllegalArgumentException if the input string format is invalid
+ */
+ public <T> T parse1D(String str, Coordinates.Factory1D<T> factory) throws IllegalArgumentException {
+ final ParsePosition pos = new ParsePosition(0);
+
+ readPrefix(str, pos);
+ final double v = readCoordinateValue(str, pos);
+ readSuffix(str, pos);
+ endParse(str, pos);
+
+ return factory.create(v);
+ }
+
+ /** Parses the given string as a 2D coordinate tuple and passes the coordinate values to the
+ * given factory. The object created by the factory is returned.
+ * @param str the string to be parsed
+ * @param factory object that will be passed the parsed coordinate values
+ * @return object created by {@code factory}
+ * @throws IllegalArgumentException if the input string format is invalid
+ */
+ public <T> T parse2D(String str, Coordinates.Factory2D<T> factory) throws IllegalArgumentException {
+ final ParsePosition pos = new ParsePosition(0);
+
+ readPrefix(str, pos);
+ final double v1 = readCoordinateValue(str, pos);
+ final double v2 = readCoordinateValue(str, pos);
+ readSuffix(str, pos);
+ endParse(str, pos);
+
+ return factory.create(v1, v2);
+ }
+
+ /** Parses the given string as a 3D coordinate tuple and passes the coordinate values to the
+ * given factory. The object created by the factory is returned.
+ * @param str the string to be parsed
+ * @param factory object that will be passed the parsed coordinate values
+ * @return object created by {@code factory}
+ * @throws IllegalArgumentException if the input string format is invalid
+ */
+ public <T> T parse3D(String str, Coordinates.Factory3D<T> factory) throws IllegalArgumentException {
+ ParsePosition pos = new ParsePosition(0);
+
+ readPrefix(str, pos);
+ final double v1 = readCoordinateValue(str, pos);
+ final double v2 = readCoordinateValue(str, pos);
+ final double v3 = readCoordinateValue(str, pos);
+ readSuffix(str, pos);
+ endParse(str, pos);
+
+ return factory.create(v1, v2, v3);
+ }
+}
diff --git a/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormatTest.java b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormatTest.java
new file mode 100644
index 0000000..820d3f6
--- /dev/null
+++ b/commons-geometry-core/src/test/java/org/apache/commons/geometry/core/util/SimpleCoordinateFormatTest.java
@@ -0,0 +1,453 @@
+/*
+ * 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.commons.geometry.core.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SimpleCoordinateFormatTest {
+
+ private static final double EPS = 1e-10;
+
+ private static final String COMMA = ",";
+ private static final String OPEN_PAREN = "(";
+ private static final String CLOSE_PAREN = ")";
+
+ private static Coordinates.Factory1D<Stub1D> FACTORY_1D = new Coordinates.Factory1D<Stub1D>() {
+
+ @Override
+ public Stub1D create(double v) {
+ Stub1D result = new Stub1D();
+ result.v = v;
+
+ return result;
+ }
+ };
+
+ private static Coordinates.Factory2D<Stub2D> FACTORY_2D = new Coordinates.Factory2D<Stub2D>() {
+
+ @Override
+ public Stub2D create(double v1, double v2) {
+ Stub2D result = new Stub2D();
+ result.v1 = v1;
+ result.v2 = v2;
+
+ return result;
+ }
+ };
+
+ private static Coordinates.Factory3D<Stub3D> FACTORY_3D = new Coordinates.Factory3D<Stub3D>() {
+
+ @Override
+ public Stub3D create(double v1, double v2, double v3) {
+ Stub3D result = new Stub3D();
+ result.v1 = v1;
+ result.v2 = v2;
+ result.v3 = v3;
+
+ return result;
+ }
+ };
+
+ @Test
+ public void testConstructor() {
+ // act
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat("|", "{", "}");
+
+ // assert
+ Assert.assertEquals("|", formatter.getSeparator());
+ Assert.assertEquals("{", formatter.getPrefix());
+ Assert.assertEquals("}", formatter.getSuffix());
+ }
+
+ @Test
+ public void testConstructor_defaultSeparator() {
+ // act
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat("{", "}");
+
+ // assert
+ Assert.assertEquals(COMMA, formatter.getSeparator());
+ Assert.assertEquals("{", formatter.getPrefix());
+ Assert.assertEquals("}", formatter.getSuffix());
+ }
+
+ @Test
+ public void testFormat1D() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ Assert.assertEquals("(1.0)", formatter.format1D(1.0));
+ Assert.assertEquals("(-1.0)", formatter.format1D(-1.0));
+ Assert.assertEquals("(NaN)", formatter.format1D(Double.NaN));
+ Assert.assertEquals("(-Infinity)", formatter.format1D(Double.NEGATIVE_INFINITY));
+ Assert.assertEquals("(Infinity)", formatter.format1D(Double.POSITIVE_INFINITY));
+ }
+
+ @Test
+ public void testFormat1D_noPrefixSuffix() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
+
+ // act/assert
+ Assert.assertEquals("1.0", formatter.format1D(1.0));
+ Assert.assertEquals("-1.0", formatter.format1D(-1.0));
+ Assert.assertEquals("NaN", formatter.format1D(Double.NaN));
+ Assert.assertEquals("-Infinity", formatter.format1D(Double.NEGATIVE_INFINITY));
+ Assert.assertEquals("Infinity", formatter.format1D(Double.POSITIVE_INFINITY));
+ }
+
+ @Test
+ public void testFormat2D() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ Assert.assertEquals("(1.0, -1.0)", formatter.format2D(1.0, -1.0));
+ Assert.assertEquals("(-1.0, 1.0)", formatter.format2D(-1.0, 1.0));
+ Assert.assertEquals("(NaN, -Infinity)", formatter.format2D(Double.NaN, Double.NEGATIVE_INFINITY));
+ Assert.assertEquals("(-Infinity, Infinity)", formatter.format2D(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+ }
+
+ @Test
+ public void testFormat2D_noPrefixSuffix() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
+
+ // act/assert
+ Assert.assertEquals("1.0, -1.0", formatter.format2D(1.0, -1.0));
+ Assert.assertEquals("-1.0, 1.0", formatter.format2D(-1.0, 1.0));
+ Assert.assertEquals("NaN, -Infinity", formatter.format2D(Double.NaN, Double.NEGATIVE_INFINITY));
+ Assert.assertEquals("-Infinity, Infinity", formatter.format2D(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+ }
+
+ @Test
+ public void testFormat3D() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ Assert.assertEquals("(1.0, 0.0, -1.0)", formatter.format3D(1.0, 0.0, -1.0));
+ Assert.assertEquals("(-1.0, 1.0, 0.0)", formatter.format3D(-1.0, 1.0, 0.0));
+ Assert.assertEquals("(NaN, -Infinity, Infinity)", formatter.format3D(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+ }
+
+ @Test
+ public void testFormat3D_noPrefixSuffix() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
+
+ // act/assert
+ Assert.assertEquals("1.0, 0.0, -1.0", formatter.format3D(1.0, 0.0, -1.0));
+ Assert.assertEquals("-1.0, 1.0, 0.0", formatter.format3D(-1.0, 1.0, 0.0));
+ Assert.assertEquals("NaN, -Infinity, Infinity", formatter.format3D(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
+ }
+
+ @Test
+ public void testFormat_longTokens() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat("||", "<<", ">>");
+
+ // act/assert
+ Assert.assertEquals("<<1.0>>", formatter.format1D(1.0));
+ Assert.assertEquals("<<1.0|| 2.0>>", formatter.format2D(1.0, 2.0));
+ Assert.assertEquals("<<1.0|| 2.0|| 3.0>>", formatter.format3D(1.0, 2.0, 3.0));
+ }
+
+ @Test
+ public void testParse1D() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ checkParse1D(formatter, "(1)", 1.0);
+ checkParse1D(formatter, "(-1)", -1.0);
+
+ checkParse1D(formatter, "(0.01)", 0.01);
+ checkParse1D(formatter, "(-1e-2)", -0.01);
+
+ checkParse1D(formatter, "(100)", 100);
+ checkParse1D(formatter, "(-1e2)", -100);
+
+ checkParse1D(formatter, " (\n 1 \t) ", 1);
+ checkParse1D(formatter, "\n ( -1 \t)\r\n", -1);
+
+ checkParse1D(formatter, "(1, )", 1.0);
+ checkParse1D(formatter, "(-1, )", -1.0);
+
+ checkParse1D(formatter, "(NaN)", Double.NaN);
+ checkParse1D(formatter, "(-Infinity)", Double.NEGATIVE_INFINITY);
+ checkParse1D(formatter, "(Infinity)", Double.POSITIVE_INFINITY);
+ }
+
+ @Test
+ public void testParse1D_noPrefixSuffix() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
+
+ // act/assert
+ checkParse1D(formatter, "1", 1.0);
+ checkParse1D(formatter, "-1", -1.0);
+
+ checkParse1D(formatter, "0.01", 0.01);
+ checkParse1D(formatter, "-1e-2", -0.01);
+
+ checkParse1D(formatter, "100", 100);
+ checkParse1D(formatter, "-1e2", -100);
+
+ checkParse1D(formatter, " \n 1 \t ", 1);
+ checkParse1D(formatter, "\n -1 \t\r\n", -1);
+
+ checkParse1D(formatter, "1, ", 1.0);
+ checkParse1D(formatter, "-1, ", -1.0);
+
+ checkParse1D(formatter, "NaN", Double.NaN);
+ checkParse1D(formatter, "-Infinity", Double.NEGATIVE_INFINITY);
+ checkParse1D(formatter, "Infinity", Double.POSITIVE_INFINITY);
+ }
+
+ @Test
+ public void testParse1D_failure() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ checkParse1DFailure(formatter, "", "expected \"(\" but found \"\" at index 0");
+ checkParse1DFailure(formatter, "(1 ", "expected \")\" but found \"\" at index 3");
+
+ checkParse1DFailure(formatter, "(abc)", "Failed to parse number from string at index 1: abc");
+
+ checkParse1DFailure(formatter, "(1) 1", "unexpected content at index 4");
+ }
+
+ @Test
+ public void testParse2D() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ checkParse2D(formatter, "(1,-2)", 1.0, -2.0);
+ checkParse2D(formatter, "(2,-1)", 2.0, -1.0);
+
+ checkParse2D(formatter, "(0.01, -0.02)", 0.01, -0.02);
+ checkParse2D(formatter, "(-1e-2,2e-2)", -0.01, 0.02);
+
+ checkParse2D(formatter, "(100, -1e2)", 100, -100);
+
+ checkParse2D(formatter, " (\n 1 , 2 \t) ", 1, 2);
+ checkParse2D(formatter, "\n ( -1 , -2 \t)\r\n", -1, -2);
+
+ checkParse2D(formatter, "(1, 2, )", 1.0, 2.0);
+ checkParse2D(formatter, "(-1, -2,)", -1.0, -2.0);
+
+ checkParse2D(formatter, "(NaN, -Infinity)", Double.NaN, Double.NEGATIVE_INFINITY);
+ checkParse2D(formatter, "(-Infinity, Infinity)", Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+ }
+
+ @Test
+ public void testParse2D_noPrefixSuffix() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
+
+ // act/assert
+ checkParse2D(formatter, "1,-2", 1.0, -2.0);
+ checkParse2D(formatter, "2,-1", 2.0, -1.0);
+
+ checkParse2D(formatter, "0.01, -0.02", 0.01, -0.02);
+ checkParse2D(formatter, "-1e-2,2e-2", -0.01, 0.02);
+
+ checkParse2D(formatter, "100, -1e2", 100, -100);
+
+ checkParse2D(formatter, " \n 1 , 2 \t ", 1, 2);
+ checkParse2D(formatter, "\n -1 , -2 \t\r\n", -1, -2);
+
+ checkParse2D(formatter, "1, 2, ", 1.0, 2.0);
+ checkParse2D(formatter, "-1, -2,", -1.0, -2.0);
+
+ checkParse2D(formatter, "NaN, -Infinity", Double.NaN, Double.NEGATIVE_INFINITY);
+ checkParse2D(formatter, "-Infinity, Infinity", Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+ }
+
+ @Test
+ public void testParse2D_failure() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ checkParse2DFailure(formatter, "", "expected \"(\" but found \"\" at index 0");
+ checkParse2DFailure(formatter, "(1, 2 ", "expected \")\" but found \"\" at index 6");
+
+ checkParse2DFailure(formatter, "(0,abc)", "Failed to parse number from string at index 3: abc");
+
+ checkParse2DFailure(formatter, "(1, 2) 1", "unexpected content at index 7");
+ }
+
+ @Test
+ public void testParse3D() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ checkParse3D(formatter, "(1,-2,3)", 1.0, -2.0, 3.0);
+ checkParse3D(formatter, "(2,-1,3)", 2.0, -1.0, 3.0);
+
+ checkParse3D(formatter, "(0.01, -0.02, 0.3)", 0.01, -0.02, 0.3);
+ checkParse3D(formatter, "(-1e-2,2e-2,-3E-1)", -0.01, 0.02, -0.3);
+
+ checkParse3D(formatter, "(100, -1e2,2E10)", 100, -100, 2e10);
+
+ checkParse3D(formatter, " (\n 1 , 2 , 3 \t) ", 1, 2, 3);
+ checkParse3D(formatter, "\n ( -1 , -2 , -3 \t)\r\n", -1, -2, -3);
+
+ checkParse3D(formatter, "(1, 2, 3, )", 1.0, 2.0, 3.0);
+ checkParse3D(formatter, "(-1, -2, -3,)", -1.0, -2.0, -3.0);
+
+ checkParse3D(formatter, "(NaN, -Infinity, Infinity)", Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+ }
+
+ @Test
+ public void testParse3D_noPrefixSuffix() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(null, null);
+
+ // act/assert
+ checkParse3D(formatter, "1,-2,3", 1.0, -2.0, 3.0);
+ checkParse3D(formatter, "2,-1,3", 2.0, -1.0, 3.0);
+
+ checkParse3D(formatter, "0.01, -0.02, 0.3", 0.01, -0.02, 0.3);
+ checkParse3D(formatter, "-1e-2,2e-2,-3E-1", -0.01, 0.02, -0.3);
+
+ checkParse3D(formatter, "100, -1e2,2E10", 100, -100, 2e10);
+
+ checkParse3D(formatter, " \n 1 , 2 , 3 \t ", 1, 2, 3);
+ checkParse3D(formatter, "\n -1 , -2 , -3 \t\r\n", -1, -2, -3);
+
+ checkParse3D(formatter, "1, 2, 3, ", 1.0, 2.0, 3.0);
+ checkParse3D(formatter, "-1, -2, -3,", -1.0, -2.0, -3.0);
+
+ checkParse3D(formatter, "NaN, -Infinity, Infinity", Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+ }
+
+ @Test
+ public void testParse3D_failure() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat(OPEN_PAREN, CLOSE_PAREN);
+
+ // act/assert
+ checkParse3DFailure(formatter, "", "expected \"(\" but found \"\" at index 0");
+ checkParse3DFailure(formatter, "(1, 2, 3", "expected \")\" but found \"\" at index 8");
+
+ checkParse3DFailure(formatter, "(0,0,abc)", "Failed to parse number from string at index 5: abc");
+
+ checkParse3DFailure(formatter, "(1, 2, 3) 1", "unexpected content at index 10");
+ }
+
+ @Test
+ public void testParse_longTokens() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat("||", "<<", ">>");
+
+ // act/assert
+ checkParse1D(formatter, "<<1.0>>", 1.0);
+ checkParse2D(formatter, "<<1.0|| 2.0>>", 1.0, 2.0);
+ checkParse3D(formatter, "<<1.0|| 2.0|| 3.0>>", 1.0, 2.0, 3.0);
+ }
+
+ @Test
+ public void testParse_longTokens_failure() {
+ // arrange
+ SimpleCoordinateFormat formatter = new SimpleCoordinateFormat("||", "<<", ">>");
+
+ // act/assert
+ checkParse1DFailure(formatter, "<", "expected \"<<\" but found \"<\" at index 0");
+ checkParse1DFailure(formatter, "<1.0>>", "expected \"<<\" but found \"<1\" at index 0");
+ checkParse2DFailure(formatter, "<<1.0| 2.0>>", "Failed to parse number from string at index 2: 1.0| 2.0");
+ checkParse3DFailure(formatter, "<<1.0|| 2.0|| 3.0>", "Failed to parse number from string at index 13: 3.0>");
+ }
+
+ private void checkParse1D(SimpleCoordinateFormat formatter, String str, double v) {
+ Stub1D result = formatter.parse1D(str, FACTORY_1D);
+
+ Assert.assertEquals(v, result.v, EPS);
+ }
+
+ private void checkParse1DFailure(SimpleCoordinateFormat formatter, String str, String msgSubstr) {
+ try {
+ formatter.parse1D(str, FACTORY_1D);
+ Assert.fail("Operation should have failed");
+ }
+ catch (IllegalArgumentException exc) {
+ String excMsg = exc.getMessage();
+ Assert.assertTrue("Expected message to contain [" + msgSubstr + "] but was [" + excMsg + "]",
+ excMsg.contains(msgSubstr));
+ }
+ }
+
+ private void checkParse2D(SimpleCoordinateFormat formatter, String str, double v1, double v2) {
+ Stub2D result = formatter.parse2D(str, FACTORY_2D);
+
+ Assert.assertEquals(v1, result.v1, EPS);
+ Assert.assertEquals(v2, result.v2, EPS);
+ }
+
+ private void checkParse2DFailure(SimpleCoordinateFormat formatter, String str, String msgSubstr) {
+ try {
+ formatter.parse2D(str, FACTORY_2D);
+ Assert.fail("Operation should have failed");
+ }
+ catch (IllegalArgumentException exc) {
+ String excMsg = exc.getMessage();
+ Assert.assertTrue("Expected message to contain [" + msgSubstr + "] but was [" + excMsg + "]",
+ excMsg.contains(msgSubstr));
+ }
+ }
+
+ private void checkParse3D(SimpleCoordinateFormat formatter, String str, double v1, double v2, double v3) {
+ Stub3D result = formatter.parse3D(str, FACTORY_3D);
+
+ Assert.assertEquals(v1, result.v1, EPS);
+ Assert.assertEquals(v2, result.v2, EPS);
+ Assert.assertEquals(v3, result.v3, EPS);
+ }
+
+ private void checkParse3DFailure(SimpleCoordinateFormat formatter, String str, String msgSubstr) {
+ try {
+ formatter.parse3D(str, FACTORY_3D);
+ Assert.fail("Operation should have failed");
+ }
+ catch (IllegalArgumentException exc) {
+ String excMsg = exc.getMessage();
+ Assert.assertTrue("Expected message to contain [" + msgSubstr + "] but was [" + excMsg + "]",
+ excMsg.contains(msgSubstr));
+ }
+ }
+
+ private static class Stub1D {
+ public double v;
+ }
+
+ private static class Stub2D {
+ public double v1;
+ public double v2;
+ }
+
+ private static class Stub3D {
+ public double v1;
+ public double v2;
+ public double v3;
+ }
+}