use of org.apache.sis.internal.util.LocalizedParseException in project sis by apache.
the class CompoundFormat method parseObject.
/**
* Creates an object from the given string representation.
* The default implementation delegates to {@link #parse(CharSequence, ParsePosition)}
* and ensures that the given string has been fully used, ignoring trailing
* {@linkplain Character#isSpaceChar(int) spaces} and
* {@linkplain Character#isISOControl(int) ISO control characters}.
*
* <div class="note"><b>Note:</b>
* The usual SIS policy, as documented in the {@link org.apache.sis.util.CharSequences} class, is to test for
* whitespaces using the {@code Character.isWhitespace(…)} method. The combination of {@code isSpaceChar(…)}
* and {@code isISOControl(…)} done in this {@code parseObject(…)} method is more permissive since it encompasses
* all whitespace characters, plus non-breaking spaces and non-white ISO controls.</div>
*
* @param text the string representation of the object to parse.
* @return the parsed object.
* @throws ParseException if an error occurred while parsing the object.
*/
@Override
public T parseObject(final String text) throws ParseException {
final ParsePosition pos = new ParsePosition(0);
final T value = parse(text, pos);
if (value != null) {
final int length = text.length();
int c, n = 0, i = pos.getIndex();
do {
if ((i += n) >= length) {
return value;
}
c = text.codePointAt(i);
n = Character.charCount(c);
} while (Character.isSpaceChar(c) || Character.isISOControl(c));
pos.setErrorIndex(i);
}
throw new LocalizedParseException(getLocale(Locale.Category.DISPLAY), getValueType(), text, pos);
}
use of org.apache.sis.internal.util.LocalizedParseException in project sis by apache.
the class TreeTableFormat method parse.
/**
* Creates a tree from the given character sequence,
* or returns {@code null} if the given text does not look like a tree for this method.
* This method can parse the trees created by the {@code format(…)} methods
* defined in this class.
*
* <div class="section">Parsing rules</div>
* <ul>
* <li>Each node shall be represented by a single line made of two parts, in that order:
* <ol>
* <li>white spaces and tree drawing characters ({@code '│'}, {@code '├'}, {@code '└'} or {@code '─'});</li>
* <li>string representations of node values, separated by the
* {@linkplain #getColumnSeparatorPattern() colunm separator}.</li>
* </ol>
* </li>
* <li>The number of spaces and drawing characters before the node values determines the node
* indentation. This indentation does not need to be a factor of the {@link #getIndentation()}
* value, but must be consistent across all the parsed tree.</li>
* <li>The indentation determines the parent of each node.</li>
* <li>Parsing stops at first empty line (ignoring whitespaces), or at the end of the given text.</li>
* </ul>
*
* <div class="section">Error index</div>
* If the given text does not seem to be a tree table, then this method returns {@code null}.
* Otherwise if parsing started but failed, then:
*
* <ul>
* <li>{@link ParsePosition#getErrorIndex()} will give the index at the beginning
* of line or beginning of cell where the error occurred, and</li>
* <li>{@link ParseException#getErrorOffset()} will give either the same value,
* or a slightly more accurate value inside the cell.</li>
* </ul>
*
* @param text the character sequence for the tree to parse.
* @param pos the position where to start the parsing.
* @return the parsed tree, or {@code null} if the given character sequence can not be parsed.
* @throws ParseException if an error occurred while parsing a node value.
*/
@Override
@SuppressWarnings("null")
public TreeTable parse(final CharSequence text, final ParsePosition pos) throws ParseException {
final Matcher matcher = getColumnSeparatorMatcher(text);
final int length = text.length();
int indexOfLineStart = pos.getIndex();
// Current index in the 'indentations' array.
int indentationLevel = 0;
// Number of spaces (ignoring drawing characters) for each level.
int[] indentations = new int[16];
// Last parsed node, having 'indentation[level]' characters before its content.
TreeTable.Node lastNode = null;
// First node found while parsing.
TreeTable.Node root = null;
final DefaultTreeTable table = new DefaultTreeTable(columnIndices != null ? columnIndices : TableColumn.NAME_MAP);
final TableColumn<?>[] columns = DefaultTreeTable.getColumns(table.columnIndices);
final Format[] formats = getFormats(columns, true);
do {
final int startNextLine = CharSequences.indexOfLineStart(text, 1, indexOfLineStart);
int endOfLine = startNextLine;
while (endOfLine > indexOfLineStart) {
final int c = text.charAt(endOfLine - 1);
if (c != '\r' && c != '\n')
break;
// Skip trailing '\r' and '\n'.
endOfLine--;
}
/*
* Skip leading spaces using Character.isSpaceChar(…) instead than isWhitespace(…)
* because we need to skip non-breaking spaces as well as ordinary space. We don't
* need to consider line feeds since they were handled by the lines just above.
*/
boolean hasChar = false;
// The indentation of current line.
int i;
for (i = indexOfLineStart; i < endOfLine; ) {
final int c = Character.codePointAt(text, i);
if (!Character.isSpaceChar(c)) {
hasChar = true;
if ("─│└├".indexOf(c) < 0) {
break;
}
}
i += Character.charCount(c);
}
if (!hasChar) {
// The line contains only whitespaces.
break;
}
/*
* Go back to the fist non-space character (should be '─'). We do that in case the
* user puts some spaces in the text of the node label, since we don't want those
* user-spaces to interfer with the calculation of indentation.
*/
int indexOfValue = i;
i = CharSequences.skipTrailingWhitespaces(text, indexOfLineStart, i) - indexOfLineStart;
/*
* Found the first character which is not part of the indentation. Create a new root
* (without parent for now) and parse the values for each column. Columns with empty
* text are not parsed (the value is left to null).
*/
final TreeTable.Node node = new DefaultTreeTable.Node(table);
matcher.region(indexOfValue, endOfLine);
for (int ci = 0; ci < columns.length; ci++) {
final boolean found = matcher.find();
int endOfColumn = found ? matcher.start() : endOfLine;
indexOfValue = CharSequences.skipLeadingWhitespaces(text, indexOfValue, endOfColumn);
int endOfValue = CharSequences.skipTrailingWhitespaces(text, indexOfValue, endOfColumn);
if (endOfValue > indexOfValue) {
final String valueText = text.subSequence(indexOfValue, endOfValue).toString();
try {
parseValue(node, columns[ci], formats[ci], valueText);
} catch (ParseException | ClassCastException e) {
// See method javadoc.
pos.setErrorIndex(indexOfValue);
if (e instanceof ParseException) {
indexOfValue += ((ParseException) e).getErrorOffset();
}
throw new LocalizedParseException(getDisplayLocale(), Errors.Keys.UnparsableStringForClass_2, new Object[] { columns[ci].getElementType(), valueText }, indexOfValue).initCause(e);
}
}
if (!found)
break;
/*
* The end of this column will be the beginning of the next column,
* after skipping the last character of the column separator.
*/
indexOfValue = matcher.end();
}
/*
* If this is the first node created so far, it will be the root.
*/
if (root == null) {
indentations[0] = i;
root = node;
} else {
int p;
while (i < (p = indentations[indentationLevel])) {
/*
* Lower indentation level: go up in the tree until we find the new parent.
* Note that lastNode.getParent() should never return null, since only the
* node at 'indentationLevel == 0' has a null parent and we check that case.
*/
if (--indentationLevel < 0) {
pos.setErrorIndex(indexOfLineStart);
throw new LocalizedParseException(getDisplayLocale(), Errors.Keys.NodeHasNoParent_1, new Object[] { node }, indexOfLineStart);
}
lastNode = lastNode.getParent();
}
if (i == p) {
/*
* The node we just created is a sibling of the previous node. This is
* illegal if level==0, in which case we have no parent. Otherwise add
* the sibling to the common parent and let the indentation level unchanged.
*/
final TreeTable.Node parent = lastNode.getParent();
if (parent == null) {
pos.setErrorIndex(indexOfLineStart);
throw new LocalizedParseException(getDisplayLocale(), Errors.Keys.NodeHasNoParent_1, new Object[] { node }, indexOfLineStart);
}
parent.getChildren().add(node);
} else if (i > p) {
/*
* The node we just created is a child of the previous node.
* Add a new indentation level.
*/
lastNode.getChildren().add(node);
if (++indentationLevel == indentations.length) {
indentations = Arrays.copyOf(indentations, indentationLevel * 2);
}
indentations[indentationLevel] = i;
}
}
lastNode = node;
indexOfLineStart = startNextLine;
} while (indexOfLineStart != length);
if (root == null) {
return null;
}
pos.setIndex(indexOfLineStart);
table.setRoot(root);
return table;
}
use of org.apache.sis.internal.util.LocalizedParseException in project sis by apache.
the class RangeFormat method parse.
/**
* Parses text from the given string to produce a range. This method use the full string.
* If there is some unparsed characters after the parsed range, then this method thrown an
* exception.
*
* @param source the text to parse.
* @return the parsed range (never {@code null}).
* @throws ParseException if the given string can not be fully parsed.
*/
public Range<?> parse(final String source) throws ParseException {
final ParsePosition pos = new ParsePosition(0);
UnconvertibleObjectException failure = null;
try {
final Range<?> range = tryParse(source, pos);
if (range != null) {
return range;
}
} catch (UnconvertibleObjectException e) {
failure = e;
}
throw new LocalizedParseException(locale, elementType, source, pos).initCause(failure);
}
use of org.apache.sis.internal.util.LocalizedParseException in project sis by apache.
the class CoordinateFormat method parse.
/**
* Parses a coordinate from the given character sequence.
* This method presumes that the coordinate reference system is the {@linkplain #getDefaultCRS() default CRS}.
* The parsing begins at the {@linkplain ParsePosition#getIndex() index} given by the {@code pos} argument.
* If parsing succeeds, then the {@code pos} index is updated to the index after the last ordinate value and
* the parsed coordinate is returned. Otherwise (if parsing fails), the {@code pos} index is left unchanged,
* the {@code pos} {@linkplain ParsePosition#getErrorIndex() error index} is set to the index of the first
* unparsable character and an exception is thrown with a similar {@linkplain ParseException#getErrorOffset()
* error index}.
*
* @param text the character sequence for the coordinate to parse.
* @param pos the index where to start the parsing.
* @return the parsed coordinate (never {@code null}).
* @throws ParseException if an error occurred while parsing the coordinate.
*/
@Override
public DirectPosition parse(final CharSequence text, final ParsePosition pos) throws ParseException {
ArgumentChecks.ensureNonNull("text", text);
ArgumentChecks.ensureNonNull("pos", pos);
final int start = pos.getIndex();
final int length = text.length();
/*
* The NumberFormat, DateFormat and AngleFormat work only on String values, not on CharSequence.
* If the given text is not a String, we will convert an arbitrarily small section of the given
* text. Note that this will require to adjust the ParsePosition indices.
*/
final int offset;
final String asString;
final ParsePosition subPos;
if (text instanceof String) {
offset = 0;
subPos = pos;
asString = (String) text;
} else {
offset = start;
subPos = new ParsePosition(0);
asString = text.subSequence(start, Math.min(start + READ_AHEAD_LIMIT, length)).toString();
}
/*
* The Format instances to be used for each ordinate values is determined by the default CRS.
* If no such CRS has been specified, then we will parse everything as plain numbers.
*/
if (lastCRS != defaultCRS) {
initialize(defaultCRS);
}
final double[] ordinates;
Format format;
final Format[] formats = this.formats;
if (formats != null) {
format = null;
ordinates = new double[formats.length];
} else {
format = getFormat(Number.class);
ordinates = new double[DEFAULT_DIMENSION];
}
/*
* For each ordinate value except the first one, we need to skip the separator.
* If we do not find the separator, we may consider that we reached the coordinate
* end ahead of time. We currently allow that only for coordinate without CRS.
*/
for (int i = 0; i < ordinates.length; i++) {
if (i != 0) {
final int end = subPos.getIndex();
int index = offset + end;
while (!CharSequences.regionMatches(text, index, parseSeparator)) {
if (index < length) {
final int c = Character.codePointAt(text, index);
if (Character.isSpaceChar(c)) {
index += Character.charCount(c);
continue;
}
}
if (formats == null) {
pos.setIndex(index);
return new GeneralDirectPosition(Arrays.copyOf(ordinates, i));
}
pos.setIndex(start);
pos.setErrorIndex(index);
throw new LocalizedParseException(getLocale(), Errors.Keys.UnexpectedCharactersAfter_2, new CharSequence[] { text.subSequence(start, end), CharSequences.token(text, index) }, index);
}
subPos.setIndex(index + parseSeparator.length() - offset);
}
/*
* At this point 'subPos' is set to the beginning of the next ordinate to parse in 'asString'.
* Parse the value as a number, angle or date, as determined from the coordinate system axis.
*/
if (formats != null) {
format = formats[i];
}
@SuppressWarnings("null") final Object object = format.parseObject(asString, subPos);
if (object == null) {
/*
* If we failed to parse, build an error message with the type that was expected for that ordinate.
* If the given CharSequence was not a String, we may need to update the error index since we tried
* to parse only a substring.
*/
Class<?> type = Number.class;
if (types != null) {
switch(types[i]) {
case LONGITUDE:
type = Longitude.class;
break;
case LATITUDE:
type = Latitude.class;
break;
case ANGLE:
type = Angle.class;
break;
case DATE:
type = Date.class;
break;
}
}
pos.setIndex(start);
if (subPos != pos) {
pos.setErrorIndex(offset + subPos.getErrorIndex());
}
throw new LocalizedParseException(getLocale(), type, text, pos);
}
double value;
if (object instanceof Angle) {
value = ((Angle) object).degrees();
} else if (object instanceof Date) {
value = ((Date) object).getTime() - epochs[i];
} else {
value = ((Number) object).doubleValue();
}
/*
* The conversions and sign reversal applied below shall be in exact reverse order than
* in the 'format(…)' method. However we have one additional step compared to format(…):
* the unit written after the ordinate value may not be the same than the unit declared
* in the CRS axis, so we have to parse the unit and convert the value before to apply
* the reverse of 'format(…)' steps.
*/
if (units != null) {
final Unit<?> target = units[i];
if (target != null) {
final int base = subPos.getIndex();
int index = base;
/*
* Skip whitespaces using Character.isSpaceChar(…), not Character.isWhitespace(…),
* because we need to skip also the non-breaking space (Characters.NO_BREAK_SPACE).
* If we can not parse the unit after those spaces, we will revert to the original
* position (absence of unit will not be considered an error).
*/
while (index < asString.length()) {
final int c = asString.codePointAt(index);
if (Character.isSpaceChar(c)) {
index += Character.charCount(c);
continue;
}
subPos.setIndex(index);
final Object unit = getFormat(Unit.class).parseObject(asString, subPos);
if (unit == null) {
subPos.setIndex(base);
subPos.setErrorIndex(-1);
} else
try {
value = ((Unit<?>) unit).getConverterToAny(target).convert(value);
} catch (IncommensurableException e) {
index += offset;
pos.setIndex(start);
pos.setErrorIndex(index);
throw (ParseException) new ParseException(e.getMessage(), index).initCause(e);
}
break;
}
}
}
if (toFormatUnit != null) {
final UnitConverter c = toFormatUnit[i];
if (c != null) {
value = c.inverse().convert(value);
}
}
if (isNegative(i)) {
value = -value;
}
ordinates[i] = value;
}
final GeneralDirectPosition position = new GeneralDirectPosition(ordinates);
position.setCoordinateReferenceSystem(defaultCRS);
return position;
}
use of org.apache.sis.internal.util.LocalizedParseException in project sis by apache.
the class AngleFormat method parse.
/**
* Parses the given string as an angle. This full string is expected to represents an
* angle value. This assumption allows {@code parse(String)} to be more tolerant than
* {@link #parse(String, ParsePosition)} regarding white spaces between degrees, minutes
* and seconds fields.
*
* @param source the string to parse.
* @return the parsed string as an {@link Angle}, {@link Latitude} or {@link Longitude} object.
* @throws ParseException if the string can not be fully parsed.
*
* @see #isFallbackAllowed()
*/
public Angle parse(final String source) throws ParseException {
final ParsePosition pos = new ParsePosition(0);
final Angle angle = parse(source, pos, true);
final int offset = pos.getIndex();
final int length = source.length();
if (skipSpaces(source, offset, length) < length) {
throw new LocalizedParseException(locale, Angle.class, source, pos);
}
return angle;
}
Aggregations