/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2002-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * 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. */ package org.geotoolkit.io.wkt; import java.util.Map; import java.util.Locale; import java.util.LinkedHashMap; import java.io.Reader; import java.io.Writer; import java.io.PrintWriter; import java.io.IOException; import java.io.EOFException; import java.text.ParseException; import org.opengis.parameter.InvalidParameterValueException; import org.opengis.referencing.crs.*; import org.opengis.referencing.datum.*; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.cs.CoordinateSystemAxis; import org.apache.sis.io.wkt.Symbols; import org.apache.sis.io.wkt.Convention; import org.geotoolkit.util.Strings; import org.apache.sis.util.CharSequences; import org.apache.sis.referencing.datum.BursaWolfParameters; import org.geotoolkit.io.ContentFormatException; import org.geotoolkit.resources.Errors; import static org.apache.sis.util.collection.Containers.isNullOrEmpty; /** * @deprecated Moved to Apache SIS as {@link org.apache.sis.io.wkt.WKTFormat}. */ @Deprecated public class WKTFormat extends org.apache.sis.io.wkt.WKTFormat { /** * For cross-version compatibility. */ private static final long serialVersionUID = -2909110214650709560L; /** * The mapping between WKT element name and the object class to be created. * Keys must be upper case. */ private static final Map<String,Class<?>> TYPES; static { final Map<String,Class<?>> map = new LinkedHashMap<>(25); map.put( "GEOGCS", GeographicCRS.class); map.put( "PROJCS", ProjectedCRS.class); map.put( "GEOCCS", GeocentricCRS.class); map.put( "VERT_CS", VerticalCRS.class); map.put( "LOCAL_CS", EngineeringCRS.class); map.put( "COMPD_CS", CompoundCRS.class); map.put( "FITTED_CS", DerivedCRS.class); map.put( "AXIS", CoordinateSystemAxis.class); map.put( "PRIMEM", PrimeMeridian.class); map.put( "TOWGS84", BursaWolfParameters.class); map.put( "SPHEROID", Ellipsoid.class); map.put( "VERT_DATUM", VerticalDatum.class); map.put( "LOCAL_DATUM", EngineeringDatum.class); map.put( "DATUM", GeodeticDatum.class); map.put( "PARAM_MT", MathTransform.class); map.put( "CONCAT_MT", MathTransform.class); map.put( "INVERSE_MT", MathTransform.class); map.put("PASSTHROUGH_MT", MathTransform.class); TYPES = map; } /** * The map of definitions. Will be created only if used. */ private Definitions definitions; /** * Constructs a format using the default factories. */ public WKTFormat() { super(null, null); super.setConvention(Convention.WKT1); } /** * Sets the symbols used for parsing and formatting WKT. * * @param symbols The new set of symbols to use for parsing and formatting WKT. */ @Override public void setSymbols(final Symbols symbols) { final Symbols old = super.getSymbols(); super.setSymbols(symbols); if (!symbols.equals(old)) { if (definitions != null) { definitions.quote = (char) symbols.getOpeningQuote(0); // TODO (need also close quote). } } } /** * Returns a map of short identifiers to substitute by WKT string before parsing. See the * "<cite>String expansion</cite>" section in <a href="#skip-navbar_top">class javadoc</a> * for details. * <p> * Entries added in the definitions map will have immediate effect in this {@code WKTFormat} * object. They must obey the following constraints: * <p> * <ul> * <li>Keys must be valid identifiers according Java rules.</li> * <li>Values must be valid WKT strings - they will be parsed.</li> * </ul> * <p> * Any attempt to put an illegal key or value in the definitions map will result in * an {@link IllegalArgumentException} being thrown. * * @return A live map of (<var>identifiers</var>, <var>WKT</var>) entries. */ public Map<String,String> definitions() { if (definitions == null) { definitions = new Definitions(this); definitions.quote = (char) getSymbols().getOpeningQuote(0); // TODO (need also close quote). } return definitions; } /** * Prints to the specified stream a table of all {@linkplain #definitions() definitions}. * The table content is inferred from the values added to the definitions map. This method * does nothing if the definitions map is empty. * * @param out writer The output stream where to write the table. * @throws IOException if an error occurred while writing to the output stream. */ public void printDefinitions(final Writer out) throws IOException { if (!isNullOrEmpty(definitions)) { definitions.print(out, getColors() != null); } } /** * Parses the specified text and ensures that the resulting object is of the specified type. * If the given text is a valid identifier and this identifier was registered in the * {@linkplain #definitions() definitions} map, then the associated object will be returned. * Otherwise the given text is parsed as a WKT. * * @param <T> The expected type of the object to be parsed. * @param text The WKT to parse, or an identifier given to the * {@linkplain #definitions() definitions} map. * @param offset The index of the first character to parse in the given text. This * information is explicitly given instead than expecting the caller to compute * {@code text.substring(offset)} in order to provide more accurate error offset * in case of {@link ParseException}. * @param type The expected type of the object to be parsed (usually a * <code>{@linkplain CoordinateReferenceSystem}.class</code> or * <code>{@linkplain MathTransform}.class</code>). * @return The parsed object. * @throws ParseException if the string can't be parsed. */ public <T> T parse(String text, final int offset, final Class<T> type) throws ParseException { Object value; text = text.substring(offset); if (Strings.isJavaIdentifier(text)) { if (definitions == null || (value = definitions.getParsed(text)) == null) { throw new ParseException(Errors.format( Errors.Keys.NoSuchAuthorityCode_2, type, text), 0); } } else { if (definitions != null) { text = definitions.substitute(text); } try { value = parseObject(text); } catch (ParseException exception) { if (definitions != null) { exception = definitions.adjustErrorOffset(exception, offset); } throw exception; } } final Class<?> actualType = value.getClass(); if (type.isAssignableFrom(actualType)) { return type.cast(value); } throw new ParseException(Errors.format( Errors.Keys.IllegalClass_2, actualType, type), 0); } /** * Reads WKT strings from an input stream and reformats them to the specified * output stream. WKT strings are read until the the end-of-stream, or until * an unparsable WKT has been hit. In this later case, an error message is * formatted to the specified error stream. * <p> * This method is useful for {@linkplain #setIndentation changing the indentation}, rewriting * the WKT using parameter names specified by a {@link #setNameAuthority different authority}, * for {@linkplain #setColors adding syntax coloring}, expanding the WKT strings according the * {@linkplain #definitions() definitions}, <i>etc.</i> * * @param in The input stream. * @param out The output stream. * @param err The error stream. * @throws IOException if an error occurred while reading from the input stream * or writing to the output stream. */ public void reformat(final Reader in, final Writer out, final PrintWriter err) throws IOException { final StringBuilder buffer = new StringBuilder(); final String lineSeparator = System.lineSeparator(); final Symbols symbols = getSymbols(); final int[] bracketCount = new int[symbols.getNumPairedBrackets()]; while (true) { /* * Skips whitespaces, stopping the method if EOF is reached. */ char c; do { final int ci = in.read(); if (ci < 0) { return; } c = (char) ci; } while (Character.isWhitespace(c)); /* * Copies next characters until the first opening bracket. Count the bracket * that we open, and stop copying characters after we have closed all of them. */ buffer.setLength(0); copy: while (true) { final int ci = in.read(); if (ci < 0) { throw new EOFException(Errors.format(Errors.Keys.UnexpectedEndOfString)); } c = (char) ci; buffer.append(c); for (int i=0; i<bracketCount.length; i++) { if (c == symbols.getOpeningBracket(i)) { bracketCount[i]++; continue copy; } } for (int i=0; i<bracketCount.length; i++) { final int closingBracket = symbols.getClosingBracket(i); if (c == closingBracket) { if (--bracketCount[i] < 0) { throw new ContentFormatException(Errors.format( Errors.Keys.NonEquilibratedParenthesis_2, closingBracket, symbols.getOpeningBracket(i))); } for (i=0; i<bracketCount.length; i++) { // NOSONAR: The outer loop will not continue. if (bracketCount[i] != 0) { continue copy; } } break copy; } } } /* * Now parses and reformats the WKT. */ final String wkt = buffer.toString(); final Object object; try { object = parseObject(wkt); } catch (ParseException exception) { err.println(exception.getLocalizedMessage()); reportError(err, wkt, exception.getErrorOffset()); continue; } catch (InvalidParameterValueException exception) { err.print(Errors.format(Errors.Keys.In_1, exception.getParameterName())); err.print(' '); err.println(exception.getLocalizedMessage()); continue; } out.write(lineSeparator); out.write(format(object)); out.write(lineSeparator); out.write(lineSeparator); out.flush(); } } /** * Reports a failure while parsing the specified line. * * @param err The stream where to report the failure. * @param line The line that failed. * @param errorOffset The error offset in the specified line. This is usually the * value provided by {@link ParseException#getErrorOffset}. */ private static void reportError(final PrintWriter err, String line, int errorOffset) { line = line.replace('\r', ' ').replace('\n', ' '); final int WINDOW_WIDTH = 80; // Arbitrary value. int stop = line.length(); int base = errorOffset - WINDOW_WIDTH/2; final int baseMax = stop - WINDOW_WIDTH; final boolean hasTrailing = (Math.max(base,0) < baseMax); if (!hasTrailing) { base = baseMax; } if (base < 0) { base = 0; } stop = Math.min(stop, base + WINDOW_WIDTH); if (hasTrailing) { stop -= 3; } if (base != 0) { err.print("..."); errorOffset += 3; base += 3; } err.print(line.substring(base, stop)); if (hasTrailing) { err.println("..."); } else { err.println(); } err.print(CharSequences.spaces(errorOffset - base)); err.println('^'); } /** * Returns the class of the specified WKT element. For example this method returns * <code>{@linkplain ProjectedCRS}.class</code> for element "{@code PROJCS}". * <p> * This method is the converse of {@link #getNameOf}. * * @param element The WKT element name. * @return The GeoAPI class of the specified element, or {@code null} if unknown. */ public static Class<?> getClassOf(String element) { if (element == null) { return null; } element = element.trim().toUpperCase(Locale.US); final Class<?> type = TYPES.get(element); assert type == null || type.equals(MathTransform.class) || element.equals(getNameOf(type)) : type; return type; } /** * Returns the WKT name of the specified object type. For example this method returns * "{@code PROJCS}" for type <code>{@linkplain ProjectedCRS}.class</code>. * <p> * This method is the converse of {@link #getClassOf}. * * @param type The GeoAPI class of the specified element. * @return The WKT element name, or {@code null} if unknown. */ public static String getNameOf(final Class<?> type) { if (type != null) { for (final Map.Entry<String,Class<?>> entry : TYPES.entrySet()) { final Class<?> candidate = entry.getValue(); if (candidate.isAssignableFrom(type)) { return entry.getKey(); } } } return null; } }