/* * 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. */ package org.geotools.referencing.wkt; import java.io.IOException; import java.io.LineNumberReader; import java.io.PrintWriter; import java.io.Writer; import java.text.Format; import java.text.ParseException; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.geotools.util.logging.Logging; import org.geotools.resources.Arguments; import org.geotools.resources.Classes; /** * Base class for application performing operations on WKT objects from the command line. * Instructions are usually read from the {@linkplain System#in standard input stream} and * results sent to the {@linkplain System#out standard output stream}, but those streams can * be redirected. The set of allowed instructions depends on the subclass used. * * @since 2.1 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public abstract class AbstractConsole implements Runnable { /** * The input stream, usually the {@linkplain System#in standard one}. */ protected final LineNumberReader in; /** * The output stream, usually the {@linkplain System#out standard one}. */ protected final Writer out; /** * The error stream, usually the {@linkplain System#err standard one}. */ protected final PrintWriter err; /** * The line separator, usually the system default. */ protected final String lineSeparator; /** * The WKT parser, usually a {@link Preprocessor} object. */ protected final Format parser; /** * The command-line prompt. */ private String prompt = "crs>"; /** * The last line read, or {@code null} if none. */ private transient String line; /** * Set to {@code true} if {@link #stop()} was invoked. */ private transient volatile boolean stop; /** * Creates a new console instance using {@linkplain System#in standard input stream}, * {@linkplain System#out standard output stream}, {@linkplain System#err error output stream} * and the system default line separator. * * @param parser The WKT parser, usually a {@link Preprocessor} object. */ public AbstractConsole(final Format parser) { this(parser, new LineNumberReader(Arguments.getReader(System.in))); } /** * Creates a new console instance using the specified input stream. * * @param parser The WKT parser, usually a {@link Preprocessor} object. * @param in The input stream. */ public AbstractConsole(final Format parser, final LineNumberReader in) { this(parser, in, Arguments.getWriter(System.out), new PrintWriter(Arguments.getWriter(System.err), true), System.getProperty("line.separator", "\n")); } /** * Creates a new console instance using the specified streams and line separator. * * @param parser The WKT parser, usually a {@link Preprocessor} object. * @param in The input stream. * @param out The output stream. * @param err The error stream. * @param lineSeparator The line separator. */ public AbstractConsole(final Format parser, final LineNumberReader in, final Writer out, final PrintWriter err, final String lineSeparator) { this.parser = parser; this.in = in; this.out = out; this.err = err; this.lineSeparator = lineSeparator; } /** * Parses the specified text. The default implementation delegates the work to the * {@linkplain #parser}. * * @param text The text, as a name, a WKT to parse, or an authority code. * @param type The expected type for the object to be parsed (usually a * <code>{@linkplain CoordinateReferenceSystem}.class</code> or * <code>{@linkplain MathTransform}.class</code>). * @return The object. * @throws ParseException if parsing the specified WKT failed. * @throws FactoryException if the object is not of the expected type. */ public Object parseObject(final String text, final Class type) throws ParseException, FactoryException { if (parser instanceof Preprocessor) { final Preprocessor parser = (Preprocessor) this.parser; parser.offset = (line!=null) ? Math.max(0, line.indexOf(text)) : 0; return parser.parseObject(text, type); } else { return parser.parseObject(text); } } /** * Adds a predefined Well Know Text (WKT). The {@code value} argument given to this method * can contains itself other definitions specified in some previous calls to this method. This * method do nothing if the {@linkplain #parser} is not an instance of {@link Preprocessor}. * * @param name The name for the definition to be added. * @param value The Well Know Text (WKT) represented by the name. * @throws IllegalArgumentException if the name is invalid. * @throws ParseException if the WKT can't be parsed. */ public void addDefinition(final String name, final String value) throws ParseException { if (parser instanceof Preprocessor) { ((Preprocessor) parser).addDefinition(name, value); } } /** * Load all definitions from the specified stream. Definitions are key-value pairs * in the form {@code name = wkt} (without the {@code SET} keyword). The * result is the same than invoking the {@code SET} instruction for each line * in the specified stream. This method is used for loading predefined objects like * the database used by {@link org.geotools.referencing.factory.PropertyAuthorityFactory}. * * @param in The input stream. * @throws IOException if an input operation failed. * @throws ParseException if a well know text (WKT) can't be parsed. */ public void loadDefinitions(final LineNumberReader in) throws IOException, ParseException { while ((line=readLine(in)) != null) { String name=line, value=null; final int i = line.indexOf('='); if (i >= 0) { name = line.substring(0,i).trim(); value = line.substring(i+1).trim(); } addDefinition(name, value); } } /** * Prints to the {@linkplain #out output stream} a table of all definitions. The content of * this table is inferred from the values given to the {@link #addDefinition} method. This * method print nothing if the {@linkplain #parser} is not an instance of {@link Preprocessor}. * * @throws IOException if an error occured while writting to the output stream. */ public void printDefinitions() throws IOException { if (parser instanceof Preprocessor) { ((Preprocessor) parser).printDefinitions(out); } } /** * Returns the command-line prompt, or {@code null} if there is none. */ public String getPrompt() { return prompt; } /** * Set the command-line prompt, or {@code null} for none. */ public void setPrompt(final String prompt) { this.prompt = prompt; } /** * Read the next line from the specified input stream. Empty lines * and comment lines are skipped. If there is no more line to read, * then this method returns {@code null}. * * @param in The input stream to read from. * @return The next non-empty and non-commented line, or {@code null} if none. * @throws IOException if the reading failed. */ private static String readLine(final LineNumberReader in) throws IOException { String line; while ((line=in.readLine()) != null) { line = line.trim(); if (line.length() == 0) { // Ignore empty lines. continue; } if (line.startsWith("//")) { // Ignore comment lines. continue; } break; } return line; } /** * Process instructions from the {@linkplain #in input stream} specified at construction * time. All lines are read until the end of stream ({@code [Ctrl-Z]} for input from * the keyboard), or until {@link #stop()} is invoked. Non-empty and non-comment lines are * given to the {@link #execute} method. Errors are catched and printed to the * {@linkplain #err error stream}. */ public void run() { try { while (!stop) { if (prompt != null) { out.write(prompt); } out.flush(); line = readLine(in); if (line == null) { break; } try { execute(line); } catch (Exception exception) { reportError(exception); } } out.flush(); stop = false; } catch (IOException exception) { reportError(exception); } } /** * Executes all instructions (like {@link #run}), but stop at the first error. * * @throws Exception if an instruction failed. */ public void executeAll() throws Exception { while ((line=readLine(in)) != null) { try { execute(line); out.flush(); } catch(Exception e) { reportError(e); throw e; } } } /** * Execute the specified instruction. * * @param instruction The instruction to execute. * @throws Exception if the instruction failed. */ protected abstract void execute(String instruction) throws Exception; /** * Stops the {@link #run} method. This method can been invoked from any thread. * If a line is in process, it will be finished before the {@link #run} method * stops. */ public void stop() { this.stop = true; } /** * Print an exception message to the {@linkplain System#err standard error stream}. * The error message includes the line number, and the column where the failure * occured in the exception is an instance of {@link ParseException}. * * @param exception The exception to report. * @todo Localize */ protected void reportError(final Exception exception) { try { out.flush(); } catch (IOException ignore) { Logging.unexpectedException(AbstractConsole.class, "reportError", ignore); } err.print(Classes.getShortClassName(exception)); err.print(" at line "); err.print(in.getLineNumber()); Throwable cause = exception; while (true) { String message = cause.getLocalizedMessage(); if (message != null) { err.print(": "); err.print(message); } err.println(); cause = cause.getCause(); if (cause == null) { break; } err.print("Caused by "); err.print(Classes.getShortClassName(cause)); } err.println("Type 'stacktrace' for stack trace information."); if (line!=null && exception instanceof ParseException) { AbstractParser.reportError(err, line, ((ParseException)exception).getErrorOffset()); } err.println(); } }