/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotools.referencing.wkt;
import java.lang.reflect.Array;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.Collection;
import java.util.Locale;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import javax.measure.unit.UnitFormat;
import javax.measure.quantity.Angle;
import javax.measure.quantity.Length;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.OperationMethod;
import org.opengis.util.CodeList;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
import org.geotools.math.XMath;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.resources.Arguments;
import org.geotools.util.Utilities;
import org.geotools.resources.X364;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
/**
* Format {@link Formattable} objects as
* <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
* Known Text</cite> (WKT)</A>.
*
* A formatter is constructed with a specified set of symbols.
* The {@linkplain Locale locale} associated with the symbols is used for querying
* {@linkplain org.opengis.metadata.citation.Citation#getTitle authority titles}.
*
* @since 2.0
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">Well Know Text specification</A>
* @see <A HREF="http://home.gdal.org/projects/opengis/wktproblems.html">OGC WKT Coordinate System Issues</A>
*/
public class Formatter {
/**
* Do not format an {@code "AUTHORITY"} element for instances of those classes.
*
* @see #authorityAllowed
*/
@SuppressWarnings("unchecked")
private static final Class<? extends IdentifiedObject>[] AUTHORITY_EXCLUDE = new Class[] {
CoordinateSystemAxis.class
};
/**
* ANSI X3.64 codes for syntax coloring. Used only if syntax coloring
* has been explicitly enabled.
*/
private static final String
NUMBER_COLOR = X364.YELLOW, // Floating point numbers only, not integers.
INTEGER_COLOR = X364.YELLOW,
UNIT_COLOR = X364.YELLOW,
AXIS_COLOR = X364.CYAN,
CODELIST_COLOR = X364.CYAN,
PARAMETER_COLOR = X364.GREEN,
METHOD_COLOR = X364.GREEN,
DATUM_COLOR = X364.GREEN,
ERROR_COLOR = X364.BACKGROUND_RED;
/**
* The symbols to use for this formatter.
*/
private final Symbols symbols;
/**
* The preferred authority for object or parameter names.
*
* @see AbstractParser#getAuthority
* @see AbstractParser#setAuthority
*/
private Citation authority = Citations.OGC;
/**
* Whatever we allow syntax coloring on ANSI X3.64 compatible terminal.
*
* @see AbstractParser#isColorEnabled
* @see AbstractParser#setColorEnabled
*/
boolean colorEnabled = false;
/**
* The unit for formatting measures, or {@code null} for the "natural" unit of each WKT
* element.
*/
private Unit<Length> linearUnit;
/**
* The unit for formatting measures, or {@code null} for the "natural" unit of each WKT
* element. This value is set for example by "GEOGCS", which force its enclosing "PRIMEM" to
* take the same units than itself.
*/
private Unit<Angle> angularUnit;
public Citation getAuthority() {
return authority;
}
public void setAuthority(Citation authority) {
this.authority = authority;
this.unitFormat = GeoToolsUnitFormat.getInstance(authority);
}
/**
* The object to use for formatting numbers.
*/
private final NumberFormat numberFormat;
/**
* The object to use for formatting units.
*/
private UnitFormat unitFormat = GeoToolsUnitFormat.getInstance(Citations.EPSG);
/**
* Dummy field position.
*/
private final FieldPosition dummy = new FieldPosition(0);
/**
* The buffer in which to format. Consider this field as private final; the only
* method to set the buffer to a new value is {@link AbstractParser#format}.
*/
StringBuffer buffer;
/**
* The starting point in the buffer. Always 0, except when used by
* {@link AbstractParser#format}.
*/
int bufferBase;
/**
* The amount of space to use in indentation, or 0 if indentation is disabled.
*/
final int indentation;
/**
* The amount of space to write on the left side of each line. This amount is increased
* by {@code indentation} every time a {@link Formattable} object is appended in a
* new indentation level.
*/
private int margin;
/**
* {@code true} if a new line were requested during the execution
* of {@link #append(Formattable)}. This is used to determine if
* {@code UNIT} and {@code AUTHORITY} elements should appears
* on a new line too.
*/
private boolean lineChanged;
/**
* {@code true} if the WKT is invalid. Similar to {@link #unformattable}, except that
* this field is reset to {@code false} after the invalid part has been processed by
* {@link #append(Formattable)}. This field is for internal use only.
*/
private boolean invalidWKT;
/**
* Non-null if the WKT is invalid. If non-null, then this field contains the interface class
* of the problematic part (e.g. {@link org.opengis.referencing.crs.EngineeringCRS}).
*/
private Class<?> unformattable;
/**
* Warning that may be produced during WKT formatting, or {@code null} if none.
*/
String warning;
/**
* Creates a new instance of the formatter with the default symbols.
*/
public Formatter() {
this(Symbols.DEFAULT, 0);
}
/**
* Creates a new instance of the formatter. The whole WKT will be formatted
* on a single line.
*
* @param symbols The symbols.
*/
public Formatter(final Symbols symbols) {
this(symbols, 0);
}
/**
* Creates a new instance of the formatter with the specified indentation width.
* The WKT will be formatted on many lines, and the indentation width will have
* the value specified to this constructor. If the specified indentation is
* {@link FormattableObject#SINGLE_LINE}, then the whole WKT will be formatted
* on a single line.
*
* @param symbols The symbols.
* @param indentation The amount of spaces to use in indentation. Typical values are 2 or 4.
*/
public Formatter(final Symbols symbols, final int indentation) {
this.symbols = symbols;
this.indentation = indentation;
if (symbols == null) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, "symbols"));
}
if (indentation < 0) {
throw new IllegalArgumentException(Errors.format(
ErrorKeys.ILLEGAL_ARGUMENT_$2, "indentation", indentation));
}
numberFormat = (NumberFormat) symbols.numberFormat.clone();
buffer = new StringBuffer();
}
/**
* Constructor for private use by {@link AbstractParser#format} only.
* This constructor help to share some objects with {@link AbstractParser}.
*/
Formatter(final Symbols symbols, final NumberFormat numberFormat) {
this.symbols = symbols;
indentation = Formattable.getIndentation();
this.numberFormat = numberFormat; // No clone needed.
// Do not set the buffer. It will be set by AbstractParser.format.
}
/**
* Set the colors using the specified ANSI escape. The color is ignored
* unless syntax coloring has been explicitly enabled. The {@code color}
* should be a constant from {@link X364}.
*/
private void setColor(final String color) {
if (colorEnabled) {
buffer.append(color);
}
}
/**
* Reset the colors to the default. This method do nothing
* unless syntax coloring has been explicitly enabled.
*/
private void resetColor() {
if (colorEnabled) {
buffer.append(X364.DEFAULT);
}
}
/**
* Returns the color to uses for the name of the specified object.
*/
private static String getNameColor(final IdentifiedObject object) {
if (object instanceof Datum) {
return DATUM_COLOR;
}
if (object instanceof OperationMethod) {
return METHOD_COLOR;
}
if (object instanceof CoordinateSystemAxis) {
return AXIS_COLOR;
}
// Note: we can't test for MathTransform here, since it is not an IdentifiedObject.
// If we want to provide a color for the MathTransform name, we would need to
// do that in 'append(String)' method, but the later is for general string...
return null;
}
/**
* Add a separator to the buffer, if needed.
*
* @param newLine if {@code true}, add a line separator too.
*/
private void appendSeparator(final boolean newLine) {
int length = buffer.length();
char c;
do {
if (length == bufferBase) {
return;
}
c = buffer.charAt(--length);
if (c==symbols.open || c==symbols.openArray) {
return;
}
} while (Character.isWhitespace(c) || c==symbols.space);
buffer.append(symbols.separator).append(symbols.space);
if (newLine && indentation != 0) {
buffer.append(System.getProperty("line.separator", "\n"))
.append(Utilities.spaces(margin));
lineChanged = true;
}
}
/**
* Append the specified {@code Formattable} object. This method will automatically append
* the keyword (e.g. <code>"GEOCS"</code>), the name and the authority code, and will invokes
* <code>formattable.{@linkplain Formattable#formatWKT formatWKT}(this)</code> for completing
* the inner part of the WKT.
*
* @param formattable The formattable object to append to the WKT.
*/
public void append(final Formattable formattable) {
/*
* Formats the opening bracket and the object name (e.g. "NAD27").
* The WKT entity name (e.g. "PROJCS") will be formatted later.
* The result of this code portion looks like the following:
*
* <previous text>,
* ["NAD27 / Idaho Central"
*/
appendSeparator(true);
int base = buffer.length();
buffer.append(symbols.open);
final IdentifiedObject info = (formattable instanceof IdentifiedObject)
? (IdentifiedObject) formattable : null;
if (info != null) {
final String c = getNameColor(info);
if (c != null) {
setColor(c);
}
buffer.append(symbols.quote).append(getName(info)).append(symbols.quote);
if (c != null) {
resetColor();
}
}
/*
* Formats the part after the object name, then insert the WKT element name
* in front of them. The result of this code portion looks like the following:
*
* <previous text>,
* PROJCS["NAD27 / Idaho Central",
* GEOGCS[...etc...],
* ...etc...
*/
indent(+1);
lineChanged = false;
String keyword = formattable.formatWKT(this);
if (colorEnabled && invalidWKT) {
invalidWKT = false;
buffer.insert(base, ERROR_COLOR + X364.BACKGROUND_DEFAULT);
base += ERROR_COLOR.length();
}
buffer.insert(base, keyword);
/*
* Formats the AUTHORITY[<name>,<code>] entity, if there is one. The entity
* will be on the same line than the enclosing one if no line separator were
* added (e.g. SPHEROID["Clarke 1866", ..., AUTHORITY["EPSG","7008"]]), or on
* a new line otherwise. After this block, the result looks like the following:
*
* <previous text>,
* PROJCS["NAD27 / Idaho Central",
* GEOGCS[...etc...],
* ...etc...
* AUTHORITY["EPSG","26769"]]
*/
final Identifier identifier = getIdentifier(info);
if (identifier!=null && authorityAllowed(info)) {
final Citation authority = identifier.getAuthority();
if (authority != null) {
/*
* Since WKT often use abbreviations, search for the shortest
* title or alternate title. If one is found, it will be used
* as the authority name (e.g. "EPSG").
*/
InternationalString inter = authority.getTitle();
String title = (inter!=null) ? inter.toString(symbols.locale) : null;
for (final InternationalString alt : authority.getAlternateTitles()) {
if (alt != null) {
final String candidate = alt.toString(symbols.locale);
if (candidate != null) {
if (title==null || candidate.length() < title.length()) {
title = candidate;
}
}
}
}
if (title != null) {
appendSeparator(lineChanged);
buffer.append("AUTHORITY")
.append(symbols.open)
.append(symbols.quote)
.append(title)
.append(symbols.quote);
final String code = identifier.getCode();
if (code != null) {
buffer.append(symbols.separator)
.append(symbols.quote)
.append(code)
.append(symbols.quote);
}
buffer.append(symbols.close);
}
}
}
buffer.append(symbols.close);
lineChanged = true;
indent(-1);
}
/**
* Append the specified OpenGIS's {@code IdentifiedObject} object.
*
* @param info The info object to append to the WKT.
*/
public void append(final IdentifiedObject info) {
if (info instanceof Formattable) {
append((Formattable) info);
} else {
append(new Adapter(info));
}
}
/**
* Append the specified math transform.
*
* @param transform The transform object to append to the WKT.
*/
public void append(final MathTransform transform) {
if (transform instanceof Formattable) {
append((Formattable) transform);
} else {
append(new Adapter(transform));
}
}
/**
* Append a code list to the WKT.
*
* @param code The code list to format.
*/
public void append(final CodeList code) {
if (code != null) {
appendSeparator(false);
setColor(CODELIST_COLOR);
final String name = code.name();
final boolean needQuotes = (name.indexOf(' ') >= 0);
if (needQuotes) {
buffer.append(symbols.quote);
}
buffer.append(name);
if (needQuotes) {
buffer.append(symbols.quote);
setInvalidWKT(code.getClass());
}
resetColor();
}
}
/**
* Append a {@linkplain ParameterValue parameter} in WKT form. If the supplied parameter
* is actually a {@linkplain ParameterValueGroup parameter group}, all parameters will be
* inlined.
*
* @param parameter The parameter to format.
*/
public void append(final GeneralParameterValue parameter) {
if (parameter instanceof ParameterValueGroup) {
for (final GeneralParameterValue param : ((ParameterValueGroup)parameter).values()) {
append(param);
}
}
if (parameter instanceof ParameterValue) {
final ParameterValue<?> param = (ParameterValue) parameter;
final ParameterDescriptor<?> descriptor = param.getDescriptor();
final Unit<?> valueUnit = descriptor.getUnit();
Unit<?> unit = valueUnit;
if (unit!=null && !Unit.ONE.equals(unit)) {
if (linearUnit!=null && unit.isCompatible(linearUnit)) {
unit = linearUnit;
} else if (angularUnit!=null && unit.isCompatible(angularUnit)) {
unit = angularUnit;
}
}
appendSeparator(true);
final int start = buffer.length();
buffer.append("PARAMETER");
final int stop = buffer.length();
buffer.append(symbols.open);
setColor(PARAMETER_COLOR);
buffer.append(symbols.quote).append(getName(descriptor)).append(symbols.quote);
resetColor();
buffer.append(symbols.separator).append(symbols.space);
if (unit != null) {
double value;
try {
value = param.doubleValue(unit);
} catch (IllegalStateException exception) {
// May happen if a parameter is mandatory (e.g. "semi-major")
// but no value has been set for this parameter.
if (colorEnabled) {
buffer.insert(stop, X364.BACKGROUND_DEFAULT).insert(start, ERROR_COLOR);
}
warning = exception.getLocalizedMessage();
value = Double.NaN;
}
if (!unit.equals(valueUnit)) {
value = XMath.trimDecimalFractionDigits(value, 4, 9);
}
format(value);
} else {
appendObject(param.getValue());
}
buffer.append(symbols.close);
}
}
/**
* Append the specified value to a string buffer. If the value is an array, then the
* array elements are appended recursively (i.e. the array may contains sub-array).
*/
private void appendObject(final Object value) {
if (value == null) {
buffer.append("null");
return;
}
if (value.getClass().isArray()) {
buffer.append(symbols.openArray);
final int length = Array.getLength(value);
for (int i=0; i<length; i++) {
if (i != 0) {
buffer.append(symbols.separator).append(symbols.space);
}
appendObject(Array.get(value, i));
}
buffer.append(symbols.closeArray);
return;
}
if (value instanceof Number) {
format((Number) value);
} else {
buffer.append(symbols.quote).append(value).append(symbols.quote);
}
}
/**
* Append an integer number. A comma (or any other element
* separator) will be written before the number if needed.
*
* @param number The integer to format.
*/
public void append(final int number) {
appendSeparator(false);
format(number);
}
/**
* Append a floating point number. A comma (or any other element
* separator) will be written before the number if needed.
*
* @param number The floating point value to format.
*/
public void append(final double number) {
appendSeparator(false);
format(number);
}
/**
* Appends a unit in WKT form. For example, {@code append(SI.KILOMETER)}
* can append "<code>UNIT["km", 1000]</code>" to the WKT.
*
* @param unit The unit to append.
*/
public void append(final Unit<?> unit) {
if (unit != null) {
appendSeparator(lineChanged);
buffer.append("UNIT").append(symbols.open);
setColor(UNIT_COLOR);
buffer.append(symbols.quote);
unitFormat.format(unit, buffer, dummy);
buffer.append(symbols.quote);
resetColor();
Unit<?> base = null;
if (SI.METER.isCompatible(unit)) {
base = SI.METER;
} else if (SI.SECOND.isCompatible(unit)) {
base = SI.SECOND;
} else if (SI.RADIAN.isCompatible(unit)) {
if (!Unit.ONE.equals(unit)) {
base = SI.RADIAN;
}
}
if (base != null) {
append(unit.getConverterTo(base).convert(1));
}
buffer.append(symbols.close);
}
}
/**
* Append a character string. The string will be written between quotes.
* A comma (or any other element separator) will be written before the string if needed.
*
* @param text The string to format.
*/
public void append(final String text) {
appendSeparator(false);
buffer.append(symbols.quote).append(text).append(symbols.quote);
}
/**
* Format an arbitrary number.
*/
private void format(final Number number) {
if (number instanceof Byte ||
number instanceof Short ||
number instanceof Integer)
{
format(number.intValue());
} else {
format(number.doubleValue());
}
}
/**
* Formats an integer number.
*/
private void format(final int number) {
setColor(INTEGER_COLOR);
final int fraction = numberFormat.getMinimumFractionDigits();
numberFormat.setMinimumFractionDigits(0);
numberFormat.format(number, buffer, dummy);
numberFormat.setMinimumFractionDigits(fraction);
resetColor();
}
/**
* Formats a floating point number.
*/
private void format(final double number) {
setColor(NUMBER_COLOR);
numberFormat.format(number, buffer, dummy);
resetColor();
}
/**
* Increase or reduce the indentation. A value of {@code +1} increase
* the indentation by the amount of spaces specified at construction time,
* and a value of {@code +1} reduce it.
*/
private void indent(final int amount) {
margin = Math.max(0, margin + indentation*amount);
}
/**
* Tells if an {@code "AUTHORITY"} element is allowed for the specified object.
*/
private static boolean authorityAllowed(final IdentifiedObject info) {
for (int i=0; i<AUTHORITY_EXCLUDE.length; i++) {
if (AUTHORITY_EXCLUDE[i].isInstance(info)) {
return false;
}
}
return true;
}
/**
* Returns the preferred identifier for the specified object. If the specified
* object contains an identifier from the preferred authority (usually
* {@linkplain Citations#OGC Open Geospatial}), then this identifier is
* returned. Otherwise, the first identifier is returned. If the specified
* object contains no identifier, then this method returns {@code null}.
*
* @param info The object to looks for a preferred identifier.
* @return The preferred identifier, or {@code null} if none.
*
* @since 2.3
*/
public Identifier getIdentifier(final IdentifiedObject info) {
Identifier first = null;
if (info != null) {
final Collection<? extends Identifier> identifiers = info.getIdentifiers();
if (identifiers != null) {
for (final Identifier id : identifiers) {
if (authorityMatches(id.getAuthority())) {
return id;
}
if (first == null) {
first = id;
}
}
}
}
return first;
}
/**
* Checks if the specified authority can be recognized as the expected authority.
* This implementation do not requires an exact matches. A matching title is enough.
*/
private boolean authorityMatches(final Citation citation) {
if (authority == citation) {
return true;
}
// The "null" locale argument is required for getting the unlocalized version.
return (citation != null) &&
authority.getTitle().toString(null).equalsIgnoreCase(
citation.getTitle().toString(null));
}
/**
* Returns the preferred name for the specified object. If the specified
* object contains a name from the preferred authority (usually
* {@linkplain Citations#OGC Open Geospatial}), then this name is
* returned. Otherwise, the first name found is returned.
*
* @param info The object to looks for a preferred name.
* @return The preferred name.
*/
public String getName(final IdentifiedObject info) {
final Identifier name = info.getName();
if (!authorityMatches(name.getAuthority())) {
final Collection<GenericName> aliases = info.getAlias();
if (aliases != null) {
/*
* The main name doesn't matches. Search in alias. We will first
* check if alias implements Identifier (this is the case of
* Geotools implementation). Otherwise, we will look at the
* scope in generic name.
*/
for (final GenericName alias : aliases) {
if (alias instanceof Identifier) {
final Identifier candidate = (Identifier) alias;
if (authorityMatches(candidate.getAuthority())) {
return candidate.getCode();
}
}
}
// The "null" locale argument is required for getting the unlocalized version.
final String title = authority.getTitle().toString(null);
for (final GenericName alias : aliases) {
final GenericName scope = alias.scope().name();
if (scope != null) {
if (title.equalsIgnoreCase(scope.toString())) {
return alias.tip().toString();
}
}
}
}
}
return name.getCode();
}
/**
* The linear unit for formatting measures, or {@code null} for the "natural" unit of each
* WKT element.
*
* @return The unit for measure. Default value is {@code null}.
*/
public Unit<Length> getLinearUnit() {
return linearUnit;
}
/**
* Set the unit for formatting linear measures.
*
* @param unit The new unit, or {@code null}.
*/
public void setLinearUnit(final Unit<Length> unit) {
if (unit!=null && !SI.METER.isCompatible(unit)) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NON_LINEAR_UNIT_$1, unit));
}
linearUnit = unit;
}
/**
* The angular unit for formatting measures, or {@code null} for the "natural" unit of
* each WKT element. This value is set for example by "GEOGCS", which force its enclosing
* "PRIMEM" to take the same units than itself.
*
* @return The unit for measure. Default value is {@code null}.
*/
public Unit<Angle> getAngularUnit() {
return angularUnit;
}
/**
* Set the angular unit for formatting measures.
*
* @param unit The new unit, or {@code null}.
*/
public void setAngularUnit(final Unit<Angle> unit) {
if (unit!=null && (!SI.RADIAN.isCompatible(unit) || Unit.ONE.equals(unit))) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NON_ANGULAR_UNIT_$1, unit));
}
angularUnit = unit;
}
/**
* Returns {@code true} if the WKT in this formatter is not strictly compliant to the
* <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">WKT
* specification</A>. This method returns {@code true} if {@link #setInvalidWKT} has
* been invoked at least once. The action to take regarding invalid WKT is caller-dependant.
* For example {@link Formattable#toString} will accepts loose WKT formatting and ignore this
* flag, while {@link Formattable#toWKT} requires strict WKT formatting and will thrown an
* exception if this flag is set.
*
* @return {@code true} if the WKT is invalid.
*/
public boolean isInvalidWKT() {
return unformattable != null || (buffer!=null && buffer.length() == 0);
/*
* Note: we really use a "and" condition (not an other "or") for the buffer test because
* the buffer is reset to 'null' by AbstractParser after a successfull formatting.
*/
}
/**
* Returns the class declared by the last call to {@link #setInvalidWKT}.
*/
final Class getUnformattableClass() {
return unformattable;
}
/**
* Set a flag marking the current WKT as not strictly compliant to the
* <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html">WKT
* specification</A>. This method is invoked by {@link Formattable#formatWKT} methods when the
* object to format is more complex than what the WKT specification allows. For example this
* method is invoked when an {@linkplain org.geotools.referencing.crs.DefaultEngineeringCRS
* engineering CRS} uses different unit for each axis, An application can tests
* {@link #isInvalidWKT} later for checking WKT validity.
*
* @param unformattable The type of the component that can't be formatted,
* for example {@link org.opengis.referencing.crs.EngineeringCRS}.
*
* @see UnformattableObjectException#getUnformattableClass
*
* @since 2.4
*/
public void setInvalidWKT(final Class<?> unformattable) {
this.unformattable = unformattable;
invalidWKT = true;
}
/**
* Returns the WKT in its current state.
*/
@Override
public String toString() {
return buffer.toString();
}
/**
* Clear this formatter. All properties (including {@linkplain #getLinearUnit unit}
* and {@linkplain #isInvalidWKT WKT validity flag} are reset to their default value.
* After this method call, this {@code Formatter} object is ready for formatting
* a new object.
*/
public void clear() {
if (buffer != null) {
buffer.setLength(0);
}
linearUnit = null;
angularUnit = null;
unformattable = null;
warning = null;
invalidWKT = false;
lineChanged = false;
margin = 0;
}
/**
* Set the preferred indentation from the command line. This indentation is used by
* {@link Formattable#toWKT()} when no indentation were explicitly requested. This
* method can be invoked from the command line using the following syntax:
*
* <blockquote>
* {@code java org.geotools.referencing.wkt.Formatter -indentation=}<var><preferred
* indentation></var>
* </blockquote>
*
* @param args The command-line arguments.
*/
public static void main(String[] args) {
final Arguments arguments = new Arguments(args);
final int indentation = arguments.getRequiredInteger(Formattable.INDENTATION);
args = arguments.getRemainingArguments(0);
Formattable.setIndentation(indentation);
}
}