SimpleTupleFormat.java
- /*
- * 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.internal;
- import java.text.ParsePosition;
- /** Class for performing simple formatting and parsing of real number tuples.
- */
- public class SimpleTupleFormat {
- /** Default value separator string. */
- private static final String DEFAULT_SEPARATOR = ",";
- /** Space character. */
- private static final String SPACE = " ";
- /** Static instance configured with default values. Tuples in this format
- * are enclosed by parentheses and separated by commas.
- */
- private static final SimpleTupleFormat DEFAULT_INSTANCE =
- new SimpleTupleFormat(",", "(", ")");
- /** String separating tuple values. */
- private final String separator;
- /** String used to signal the start of a tuple; may be null. */
- private final String prefix;
- /** String used to signal the end of a tuple; may be null. */
- private final String suffix;
- /** Constructs a new instance with the default string separator (a comma)
- * and the given prefix and suffix.
- * @param prefix String used to signal the start of a tuple; if null, no
- * string is expected at the start of the tuple
- * @param suffix String used to signal the end of a tuple; if null, no
- * string is expected at the end of the tuple
- */
- public SimpleTupleFormat(final String prefix, final String suffix) {
- this(DEFAULT_SEPARATOR, prefix, suffix);
- }
- /** Simple constructor.
- * @param separator String used to separate tuple values; must not be null.
- * @param prefix String used to signal the start of a tuple; if null, no
- * string is expected at the start of the tuple
- * @param suffix String used to signal the end of a tuple; if null, no
- * string is expected at the end of the tuple
- */
- protected SimpleTupleFormat(final String separator, final String prefix, final String suffix) {
- this.separator = separator;
- this.prefix = prefix;
- this.suffix = suffix;
- }
- /** Return the string used to separate tuple values.
- * @return the value separator string
- */
- public String getSeparator() {
- return separator;
- }
- /** Return the string used to signal the start of a tuple. This value may be null.
- * @return the string used to begin each tuple or null
- */
- public String getPrefix() {
- return prefix;
- }
- /** Returns the string used to signal the end of a tuple. This value may be null.
- * @return the string used to end each tuple or null
- */
- public String getSuffix() {
- return suffix;
- }
- /** Return a tuple string with the given value.
- * @param a value
- * @return 1-tuple string
- */
- public String format(final double a) {
- final StringBuilder sb = new StringBuilder();
- if (prefix != null) {
- sb.append(prefix);
- }
- sb.append(a);
- if (suffix != null) {
- sb.append(suffix);
- }
- return sb.toString();
- }
- /** Return a tuple string with the given values.
- * @param a1 first value
- * @param a2 second value
- * @return 2-tuple string
- */
- public String format(final double a1, final double a2) {
- final StringBuilder sb = new StringBuilder();
- if (prefix != null) {
- sb.append(prefix);
- }
- sb.append(a1)
- .append(separator)
- .append(SPACE)
- .append(a2);
- if (suffix != null) {
- sb.append(suffix);
- }
- return sb.toString();
- }
- /** Return a tuple string with the given values.
- * @param a1 first value
- * @param a2 second value
- * @param a3 third value
- * @return 3-tuple string
- */
- public String format(final double a1, final double a2, final double a3) {
- final StringBuilder sb = new StringBuilder();
- if (prefix != null) {
- sb.append(prefix);
- }
- sb.append(a1)
- .append(separator)
- .append(SPACE)
- .append(a2)
- .append(separator)
- .append(SPACE)
- .append(a3);
- if (suffix != null) {
- sb.append(suffix);
- }
- return sb.toString();
- }
- /** Return a tuple string with the given values.
- * @param a1 first value
- * @param a2 second value
- * @param a3 third value
- * @param a4 fourth value
- * @return 4-tuple string
- */
- public String format(final double a1, final double a2, final double a3, final double a4) {
- final StringBuilder sb = new StringBuilder();
- if (prefix != null) {
- sb.append(prefix);
- }
- sb.append(a1)
- .append(separator)
- .append(SPACE)
- .append(a2)
- .append(separator)
- .append(SPACE)
- .append(a3)
- .append(separator)
- .append(SPACE)
- .append(a4);
- if (suffix != null) {
- sb.append(suffix);
- }
- return sb.toString();
- }
- /** Parse the given string as a 1-tuple and passes the tuple values to the
- * given function. The function output is returned.
- * @param <T> function return type
- * @param str the string to be parsed
- * @param fn function that will be passed the parsed tuple values
- * @return object returned by {@code fn}
- * @throws IllegalArgumentException if the input string format is invalid
- */
- public <T> T parse(final String str, final DoubleFunction1N<T> fn) {
- final ParsePosition pos = new ParsePosition(0);
- readPrefix(str, pos);
- final double v = readTupleValue(str, pos);
- readSuffix(str, pos);
- endParse(str, pos);
- return fn.apply(v);
- }
- /** Parse the given string as a 2-tuple and passes the tuple values to the
- * given function. The function output is returned.
- * @param <T> function return type
- * @param str the string to be parsed
- * @param fn function that will be passed the parsed tuple values
- * @return object returned by {@code fn}
- * @throws IllegalArgumentException if the input string format is invalid
- */
- public <T> T parse(final String str, final DoubleFunction2N<T> fn) {
- final ParsePosition pos = new ParsePosition(0);
- readPrefix(str, pos);
- final double v1 = readTupleValue(str, pos);
- final double v2 = readTupleValue(str, pos);
- readSuffix(str, pos);
- endParse(str, pos);
- return fn.apply(v1, v2);
- }
- /** Parse the given string as a 3-tuple and passes the parsed values to the
- * given function. The function output is returned.
- * @param <T> function return type
- * @param str the string to be parsed
- * @param fn function that will be passed the parsed tuple values
- * @return object returned by {@code fn}
- * @throws IllegalArgumentException if the input string format is invalid
- */
- public <T> T parse(final String str, final DoubleFunction3N<T> fn) {
- final ParsePosition pos = new ParsePosition(0);
- readPrefix(str, pos);
- final double v1 = readTupleValue(str, pos);
- final double v2 = readTupleValue(str, pos);
- final double v3 = readTupleValue(str, pos);
- readSuffix(str, pos);
- endParse(str, pos);
- return fn.apply(v1, v2, v3);
- }
- /** Read the configured prefix from the current position in the given string, ignoring any preceding
- * whitespace, and advance 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
- */
- private void readPrefix(final String str, final ParsePosition pos) {
- if (prefix != null) {
- consumeWhitespace(str, pos);
- readSequence(str, prefix, pos);
- }
- }
- /** Read and return a tuple 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 tuple value
- * @throws IllegalArgumentException if the configured prefix is not null and is not found at the current
- * parsing position, ignoring preceding whitespace
- */
- private double readTupleValue(final String str, final ParsePosition pos) {
- 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();
- }
- }
- final String substr = str.substring(startIdx, endIdx);
- try {
- final double value = Double.parseDouble(substr);
- // advance the position and move past any terminating separator
- pos.setIndex(endIdx);
- matchSequence(str, separator, pos);
- return value;
- } catch (final NumberFormatException exc) {
- throw parseFailure(String.format("unable to parse number from string \"%s\"", substr), str, pos, exc);
- }
- }
- /** Read the configured suffix from the current position in the given string, ignoring any preceding
- * whitespace, and advance 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
- */
- private void readSuffix(final String str, final ParsePosition pos) {
- if (suffix != null) {
- consumeWhitespace(str, pos);
- readSequence(str, suffix, pos);
- }
- }
- /** End 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
- */
- private void endParse(final String str, final ParsePosition pos) {
- consumeWhitespace(str, pos);
- if (pos.getIndex() != str.length()) {
- throw parseFailure("unexpected content", str, pos);
- }
- }
- /** Advance {@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
- */
- private void consumeWhitespace(final String str, final 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);
- }
- /** Return 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
- */
- private boolean matchSequence(final String str, final String seq, final 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;
- }
- /** Read 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}
- */
- private void readSequence(final String str, final String seq, final ParsePosition pos) {
- if (!matchSequence(str, seq, pos)) {
- final int idx = pos.getIndex();
- final String actualSeq = str.substring(idx, Math.min(str.length(), idx + seq.length()));
- throw parseFailure(String.format("expected \"%s\" but found \"%s\"", seq, actualSeq), str, pos);
- }
- }
- /** Return an instance configured with default values. Tuples in this format
- * are enclosed by parentheses and separated by commas.
- *
- * Ex:
- * <pre>
- * "(1.0)"
- * "(1.0, 2.0)"
- * "(1.0, 2.0, 3.0)"
- * </pre>
- * @return instance configured with default values
- */
- public static SimpleTupleFormat getDefault() {
- return DEFAULT_INSTANCE;
- }
- /** Return an {@link IllegalArgumentException} representing a parsing failure.
- * @param msg the error message
- * @param str the string being parsed
- * @param pos the current parse position
- * @return an exception signaling a parse failure
- */
- private static IllegalArgumentException parseFailure(final String msg, final String str, final ParsePosition pos) {
- return parseFailure(msg, str, pos, null);
- }
- /** Return an {@link IllegalArgumentException} representing a parsing failure.
- * @param msg the error message
- * @param str the string being parsed
- * @param pos the current parse position
- * @param cause the original cause of the error
- * @return an exception signaling a parse failure
- */
- private static IllegalArgumentException parseFailure(final String msg, final String str, final ParsePosition pos,
- final Throwable cause) {
- final String fullMsg = String.format("Failed to parse string \"%s\" at index %d: %s",
- str, pos.getIndex(), msg);
- return new TupleParseException(fullMsg, cause);
- }
- /** Exception class for errors occurring during tuple parsing.
- */
- private static class TupleParseException extends IllegalArgumentException {
- /** Serializable version identifier. */
- private static final long serialVersionUID = 20180629;
- /** Simple constructor.
- * @param msg the exception message
- * @param cause the exception root cause
- */
- TupleParseException(final String msg, final Throwable cause) {
- super(msg, cause);
- }
- }
- }