001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: *
005: * (C) 2004-2006, Geotools Project Managment Committee (PMC)
006: * (C) 2004, Institut de Recherche pour le Développement
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation; either
011: * version 2.1 of the License, or (at your option) any later version.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.referencing.wkt;
019:
020: // J2SE dependencies
021: import java.io.IOException;
022: import java.io.LineNumberReader;
023: import java.io.PrintWriter;
024: import java.io.Writer;
025: import java.text.Format;
026: import java.text.ParseException;
027:
028: // OpenGIS dependencies
029: import org.opengis.referencing.FactoryException;
030: import org.opengis.referencing.crs.CoordinateReferenceSystem;
031: import org.opengis.referencing.operation.MathTransform;
032:
033: // Geotools dependencies
034: import org.geotools.util.logging.Logging;
035: import org.geotools.resources.Arguments;
036: import org.geotools.resources.Utilities;
037:
038: /**
039: * Base class for application performing operations on WKT objects from the command line.
040: * Instructions are usually read from the {@linkplain System#in standard input stream} and
041: * results sent to the {@linkplain System#out standard output stream}, but those streams can
042: * be redirected. The set of allowed instructions depends on the subclass used.
043: *
044: * @since 2.1
045: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/wkt/AbstractConsole.java $
046: * @version $Id: AbstractConsole.java 27848 2007-11-12 13:10:32Z desruisseaux $
047: * @author Martin Desruisseaux
048: */
049: public abstract class AbstractConsole implements Runnable {
050: /**
051: * The input stream, usually the {@linkplain System#in standard one}.
052: */
053: protected final LineNumberReader in;
054:
055: /**
056: * The output stream, usually the {@linkplain System#out standard one}.
057: */
058: protected final Writer out;
059:
060: /**
061: * The error stream, usually the {@linkplain System#err standard one}.
062: */
063: protected final PrintWriter err;
064:
065: /**
066: * The line separator, usually the system default.
067: */
068: protected final String lineSeparator;
069:
070: /**
071: * The WKT parser, usually a {@link Preprocessor} object.
072: */
073: protected final Format parser;
074:
075: /**
076: * The command-line prompt.
077: */
078: private String prompt = "crs>";
079:
080: /**
081: * The last line read, or {@code null} if none.
082: */
083: private transient String line;
084:
085: /**
086: * Set to {@code true} if {@link #stop()} was invoked.
087: */
088: private transient volatile boolean stop;
089:
090: /**
091: * Creates a new console instance using {@linkplain System#in standard input stream},
092: * {@linkplain System#out standard output stream}, {@linkplain System#err error output stream}
093: * and the system default line separator.
094: *
095: * @param parser The WKT parser, usually a {@link Preprocessor} object.
096: */
097: public AbstractConsole(final Format parser) {
098: this (parser, new LineNumberReader(Arguments
099: .getReader(System.in)));
100: }
101:
102: /**
103: * Creates a new console instance using the specified input stream.
104: *
105: * @param parser The WKT parser, usually a {@link Preprocessor} object.
106: * @param in The input stream.
107: */
108: public AbstractConsole(final Format parser,
109: final LineNumberReader in) {
110: this (parser, in, Arguments.getWriter(System.out),
111: new PrintWriter(Arguments.getWriter(System.err), true),
112: System.getProperty("line.separator", "\n"));
113: }
114:
115: /**
116: * Creates a new console instance using the specified streams and line separator.
117: *
118: * @param parser The WKT parser, usually a {@link Preprocessor} object.
119: * @param in The input stream.
120: * @param out The output stream.
121: * @param err The error stream.
122: * @param lineSeparator The line separator.
123: */
124: public AbstractConsole(final Format parser,
125: final LineNumberReader in, final Writer out,
126: final PrintWriter err, final String lineSeparator) {
127: this .parser = parser;
128: this .in = in;
129: this .out = out;
130: this .err = err;
131: this .lineSeparator = lineSeparator;
132: }
133:
134: /**
135: * Parses the specified text. The default implementation delegates the work to the
136: * {@linkplain #parser}.
137: *
138: * @param text The text, as a name, a WKT to parse, or an authority code.
139: * @param type The expected type for the object to be parsed (usually a
140: * <code>{@linkplain CoordinateReferenceSystem}.class</code> or
141: * <code>{@linkplain MathTransform}.class</code>).
142: * @return The object.
143: * @throws ParseException if parsing the specified WKT failed.
144: * @throws FactoryException if the object is not of the expected type.
145: */
146: public Object parseObject(final String text, final Class type)
147: throws ParseException, FactoryException {
148: if (parser instanceof Preprocessor) {
149: final Preprocessor parser = (Preprocessor) this .parser;
150: parser.offset = (line != null) ? Math.max(0, line
151: .indexOf(text)) : 0;
152: return parser.parseObject(text, type);
153: } else {
154: return parser.parseObject(text);
155: }
156: }
157:
158: /**
159: * Adds a predefined Well Know Text (WKT). The {@code value} argument given to this method
160: * can contains itself other definitions specified in some previous calls to this method. This
161: * method do nothing if the {@linkplain #parser} is not an instance of {@link Preprocessor}.
162: *
163: * @param name The name for the definition to be added.
164: * @param value The Well Know Text (WKT) represented by the name.
165: * @throws IllegalArgumentException if the name is invalid.
166: * @throws ParseException if the WKT can't be parsed.
167: */
168: public void addDefinition(final String name, final String value)
169: throws ParseException {
170: if (parser instanceof Preprocessor) {
171: ((Preprocessor) parser).addDefinition(name, value);
172: }
173: }
174:
175: /**
176: * Load all definitions from the specified stream. Definitions are key-value pairs
177: * in the form {@code name = wkt} (without the {@code SET} keyword). The
178: * result is the same than invoking the {@code SET} instruction for each line
179: * in the specified stream. This method is used for loading predefined objects like
180: * the database used by {@link org.geotools.referencing.factory.PropertyAuthorityFactory}.
181: *
182: * @param in The input stream.
183: * @throws IOException if an input operation failed.
184: * @throws ParseException if a well know text (WKT) can't be parsed.
185: */
186: public void loadDefinitions(final LineNumberReader in)
187: throws IOException, ParseException {
188: while ((line = readLine(in)) != null) {
189: String name = line, value = null;
190: final int i = line.indexOf('=');
191: if (i >= 0) {
192: name = line.substring(0, i).trim();
193: value = line.substring(i + 1).trim();
194: }
195: addDefinition(name, value);
196: }
197: }
198:
199: /**
200: * Prints to the {@linkplain #out output stream} a table of all definitions. The content of
201: * this table is inferred from the values given to the {@link #addDefinition} method. This
202: * method print nothing if the {@linkplain #parser} is not an instance of {@link Preprocessor}.
203: *
204: * @throws IOException if an error occured while writting to the output stream.
205: */
206: public void printDefinitions() throws IOException {
207: if (parser instanceof Preprocessor) {
208: ((Preprocessor) parser).printDefinitions(out);
209: }
210: }
211:
212: /**
213: * Returns the command-line prompt, or {@code null} if there is none.
214: */
215: public String getPrompt() {
216: return prompt;
217: }
218:
219: /**
220: * Set the command-line prompt, or {@code null} for none.
221: */
222: public void setPrompt(final String prompt) {
223: this .prompt = prompt;
224: }
225:
226: /**
227: * Read the next line from the specified input stream. Empty lines
228: * and comment lines are skipped. If there is no more line to read,
229: * then this method returns {@code null}.
230: *
231: * @param in The input stream to read from.
232: * @return The next non-empty and non-commented line, or {@code null} if none.
233: * @throws IOException if the reading failed.
234: */
235: private static String readLine(final LineNumberReader in)
236: throws IOException {
237: String line;
238: while ((line = in.readLine()) != null) {
239: line = line.trim();
240: if (line.length() == 0) {
241: // Ignore empty lines.
242: continue;
243: }
244: if (line.startsWith("//")) {
245: // Ignore comment lines.
246: continue;
247: }
248: break;
249: }
250: return line;
251: }
252:
253: /**
254: * Process instructions from the {@linkplain #in input stream} specified at construction
255: * time. All lines are read until the end of stream ({@code [Ctrl-Z]} for input from
256: * the keyboard), or until {@link #stop()} is invoked. Non-empty and non-comment lines are
257: * given to the {@link #execute} method. Errors are catched and printed to the
258: * {@linkplain #err error stream}.
259: */
260: public void run() {
261: try {
262: while (!stop) {
263: if (prompt != null) {
264: out.write(prompt);
265: }
266: out.flush();
267: line = readLine(in);
268: if (line == null) {
269: break;
270: }
271: try {
272: execute(line);
273: } catch (Exception exception) {
274: reportError(exception);
275: }
276: }
277: out.flush();
278: stop = false;
279: } catch (IOException exception) {
280: reportError(exception);
281: }
282: }
283:
284: /**
285: * Executes all instructions (like {@link #run}), but stop at the first error.
286: *
287: * @throws Exception if an instruction failed.
288: */
289: public void executeAll() throws Exception {
290: while ((line = readLine(in)) != null) {
291: execute(line);
292: out.flush();
293: }
294: }
295:
296: /**
297: * Execute the specified instruction.
298: *
299: * @param instruction The instruction to execute.
300: * @throws Exception if the instruction failed.
301: */
302: protected abstract void execute(String instruction)
303: throws Exception;
304:
305: /**
306: * Stops the {@link #run} method. This method can been invoked from any thread.
307: * If a line is in process, it will be finished before the {@link #run} method
308: * stops.
309: */
310: public void stop() {
311: this .stop = true;
312: }
313:
314: /**
315: * Print an exception message to the {@linkplain System#err standard error stream}.
316: * The error message includes the line number, and the column where the failure
317: * occured in the exception is an instance of {@link ParseException}.
318: *
319: * @param exception The exception to report.
320: * @todo Localize
321: */
322: protected void reportError(final Exception exception) {
323: try {
324: out.flush();
325: } catch (IOException ignore) {
326: Logging.unexpectedException("org.geotools.referencing.wkt",
327: AbstractConsole.class, "reportError", ignore);
328: }
329: err.print(Utilities.getShortClassName(exception));
330: err.print(" at line ");
331: err.print(in.getLineNumber());
332: Throwable cause = exception;
333: while (true) {
334: String message = cause.getLocalizedMessage();
335: if (message != null) {
336: err.print(": ");
337: err.print(message);
338: }
339: err.println();
340: cause = cause.getCause();
341: if (cause == null) {
342: break;
343: }
344: err.print("Caused by ");
345: err.print(Utilities.getShortClassName(cause));
346: }
347: err.println("Type 'stacktrace' for stack trace information.");
348: if (line != null && exception instanceof ParseException) {
349: AbstractParser.reportError(err, line,
350: ((ParseException) exception).getErrorOffset());
351: }
352: err.println();
353: }
354: }
|