001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.image.io.text;
018:
019: import java.io.*; // Many imports, including some for javadoc only.
020: import java.net.URL;
021: import java.net.URLConnection;
022: import java.nio.charset.Charset;
023: import java.util.Locale;
024: import javax.imageio.spi.ImageReaderSpi;
025: import javax.imageio.stream.ImageInputStream;
026:
027: import org.geotools.io.LineFormat;
028: import org.geotools.image.io.StreamImageReader;
029: import org.geotools.resources.i18n.Vocabulary;
030: import org.geotools.resources.i18n.VocabularyKeys;
031:
032: /**
033: * Base class for text image decoders. "Text images" are usually ASCII files containing pixel
034: * as geophysical values. This base class provides a convenient way to get {@link BufferedReader}
035: * for reading lines.
036: * <p>
037: * {@code TextImageReader} accepts many input types, including {@link File}, {@link URL},
038: * {@link Reader}, {@link InputStream} and {@link ImageInputStream}. The {@link Spi} provider
039: * automatically advises those input types. The above cited {@code Spi} provided also provides
040: * a convenient way to control the character encoding, with the {@link Spi#charset charset} field.
041: * Developer can gain yet more control on character encoding by overriding the {@link #getCharset}
042: * method.
043: *
044: * @since 2.4
045: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/text/TextImageReader.java $
046: * @version $Id: TextImageReader.java 26761 2007-08-29 21:25:45Z desruisseaux $
047: * @author Martin Desruisseaux
048: */
049: public abstract class TextImageReader extends StreamImageReader {
050: /**
051: * {@link #input} as a reader, or {@code null} if none.
052: *
053: * @see #getReader
054: */
055: private BufferedReader reader;
056:
057: /**
058: * Constructs a new image reader.
059: *
060: * @param provider The provider that is invoking this constructor, or {@code null} if none.
061: */
062: protected TextImageReader(final ImageReaderSpi provider) {
063: super (provider);
064: }
065:
066: /**
067: * Returns the character set to use for decoding the string from the input stream. The default
068: * implementation returns the {@linkplain Spi#charset character set} specified to the
069: * {@link Spi} object given to this {@code TextImageReader} constructor. Subclasses can
070: * override this method if they want to detect the character encoding in some other way.
071: *
072: * @param input The input stream.
073: * @return The character encoding, or {@code null} for the platform default encoding.
074: * @throws IOException If reading from the input stream failed.
075: *
076: * @see Spi#charset
077: */
078: protected Charset getCharset(final InputStream input)
079: throws IOException {
080: return (originatingProvider instanceof Spi) ? ((Spi) originatingProvider).charset
081: : null;
082: }
083:
084: /**
085: * Returns the line format to use for parsing every lines in the input stream. The default
086: * implementation creates a new {@link LineFormat} instance using the locale specified by
087: * {@link Spi#locale}. Subclasses should override this method if they want more control
088: * on the parser to be created.
089: *
090: * @param imageIndex the index of the image to be queried.
091: * @throws IOException If reading from the input stream failed.
092: *
093: * @see Spi#locale
094: */
095: protected LineFormat getLineFormat(final int imageIndex)
096: throws IOException {
097: if (originatingProvider instanceof Spi) {
098: final Locale locale = ((Spi) originatingProvider).locale;
099: if (locale != null) {
100: return new LineFormat(locale);
101: }
102: }
103: return new LineFormat();
104: }
105:
106: /**
107: * Returns the pad value for missing data, or {@link Double#NaN} if none. The pad value will
108: * applies to all columns except the one for {@link TextRecordImageReader#getColumnX x} and
109: * {@link TextRecordImageReader#getColumnY y} values, if any.
110: * <p>
111: * The default implementation returns the pad value specified to the {@link Spi} object given
112: * to this {@code TextImageReader} constructor. Subclasses can override this method if they
113: * want to detect the pad value in some other way.
114: *
115: * @param imageIndex the index of the image to be queried.
116: * @throws IOException If reading from the input stream failed.
117: *
118: * @see Spi#padValue
119: *
120: * @deprecated Should be specified in metadata instead, and implementations should use
121: * {@code SampleConverter}.
122: */
123: protected double getPadValue(final int imageIndex)
124: throws IOException {
125: return (originatingProvider instanceof Spi) ? ((Spi) originatingProvider).padValue
126: : Double.NaN;
127: }
128:
129: /**
130: * Returns the {@linkplain #input input} as an {@linkplain BufferedReader buffered reader}.
131: * If the input is already a buffered reader, it is returned unchanged. Otherwise this method
132: * creates a new {@linkplain LineNumberReader line number reader} from various input types
133: * including {@link File}, {@link URL}, {@link URLConnection}, {@link Reader},
134: * {@link InputStream} and {@link ImageInputStream}.
135: * <p>
136: * This method creates a new {@linkplain BufferedReader reader} only when first invoked.
137: * All subsequent calls will returns the same instance. Consequently, the returned reader
138: * should never be closed by the caller. It may be {@linkplain #close closed} automatically
139: * when {@link #setInput setInput(...)}, {@link #reset() reset()} or {@link #dispose()
140: * dispose()} methods are invoked.
141: *
142: * @return {@link #getInput} as a {@link BufferedReader}.
143: * @throws IllegalStateException if the {@linkplain #input input} is not set.
144: * @throws IOException If the input stream can't be created for an other reason.
145: *
146: * @see #getInput
147: * @see #getInputStream
148: */
149: protected BufferedReader getReader() throws IllegalStateException,
150: IOException {
151: if (reader == null) {
152: final Object input = getInput();
153: if (input instanceof BufferedReader) {
154: reader = (BufferedReader) input;
155: closeOnReset = null; // We don't own the underlying reader, so don't close it.
156: } else if (input instanceof Reader) {
157: reader = new LineReader((Reader) input);
158: closeOnReset = null; // We don't own the underlying reader, so don't close it.
159: } else {
160: final InputStream stream = getInputStream();
161: reader = new LineReader(getInputStreamReader(stream));
162: if (closeOnReset == stream) {
163: closeOnReset = reader;
164: }
165: }
166: }
167: return reader;
168: }
169:
170: /**
171: * Returns the specified {@link InputStream} as a {@link Reader}.
172: */
173: final Reader getInputStreamReader(final InputStream stream)
174: throws IOException {
175: final Charset charset = getCharset(stream);
176: return (charset != null) ? new InputStreamReader(stream,
177: charset) : new InputStreamReader(stream);
178: }
179:
180: /**
181: * Returns {@code true} if the specified line is a comment. This method is invoked automatically
182: * during a {@link #read read} operation. The default implementation returns {@code true} if the
183: * line is empty or if the first non-whitespace character is {@code '#'}, and {@code false}
184: * otherwise. Override this method if comment lines should be determined in a different way.
185: *
186: * @param line A line to be parsed.
187: * @return {@code true} if the line is a comment and should be ignored, or {@code false} if it
188: * should be parsed.
189: */
190: protected boolean isComment(final String line) {
191: final int length = line.length();
192: for (int i = 0; i < length; i++) {
193: final char c = line.charAt(i);
194: if (!Character.isSpaceChar(c)) {
195: return (c == '#');
196: }
197: }
198: return true;
199: }
200:
201: /**
202: * Retourne une approximation du nombre d'octets du flot occupés par les
203: * images {@code fromImage} inclusivement jusqu'à {@code toImage}
204: * exclusivement. L'implémentation par défaut calcule cette longueur en
205: * supposant que toutes les images se divisent la longueur totale du flot
206: * en parts égales.
207: *
208: * @param fromImage Index de la première image à prendre en compte.
209: * @param toImage Index suivant celui de la dernière image à prendre en
210: * compte, ou -1 pour prendre en compte toutes les images
211: * restantes jusqu'à la fin du flot.
212: * @return Le nombre d'octets occupés par les images spécifiés, ou -1 si
213: * cette longueur n'a pas pu être calculée. Si le calcul précis de
214: * cette longueur serait prohibitif, cette méthode est autorisée à
215: * retourner une simple approximation ou même à retourner la longueur
216: * totale du flot.
217: * @throws IOException si une erreur est survenue lors de la lecture du flot.
218: */
219: final long getStreamLength(final int fromImage, int toImage)
220: throws IOException {
221: long length = getStreamLength();
222: if (length > 0) {
223: final int numImages = getNumImages(false);
224: if (numImages > 0) {
225: if (toImage == -1) {
226: toImage = numImages;
227: }
228: if (fromImage < 0 || fromImage > numImages) {
229: throw new IndexOutOfBoundsException(String
230: .valueOf(fromImage));
231: }
232: if (toImage < 0 || toImage > numImages) {
233: throw new IndexOutOfBoundsException(String
234: .valueOf(toImage));
235: }
236: if (fromImage > toImage) {
237: throw new IllegalArgumentException();
238: }
239: return length * (toImage - fromImage) / numImages;
240: }
241: }
242: return length;
243: }
244:
245: /**
246: * Retourne la position du flot spécifié, ou {@code -1} si cette position est
247: * inconnue. Note: la position retournée est <strong>approximative</strong>.
248: * Elle est utile pour afficher un rapport des progrès, mais sans plus.
249: *
250: * @param reader Flot dont on veut connaître la position.
251: * @return Position approximative du flot, ou {@code -1}
252: * si cette position n'a pas pu être obtenue.
253: * @throws IOException si l'opération a échouée.
254: */
255: static long getStreamPosition(final Reader reader)
256: throws IOException {
257: return (reader instanceof LineReader) ? ((LineReader) reader)
258: .getPosition() : -1;
259: }
260:
261: /**
262: * Returns a string representation of the current stream position. For example this method
263: * may returns something like {@code "Line 14 in file HUV18204.asc"}. This method returns
264: * {@code null} if the stream position is unknown.
265: *
266: * @param message An optional message to append to the stream position, or {@code null}
267: * if none.
268: */
269: protected String getPositionString(final String message) {
270: final String file;
271: final Object input = getInput();
272: if (input instanceof File) {
273: file = ((File) input).getName();
274: } else if (input instanceof URL) {
275: file = ((URL) input).getFile();
276: } else {
277: file = null;
278: }
279: final Integer line = (reader instanceof LineNumberReader) ? new Integer(
280: ((LineNumberReader) reader).getLineNumber())
281: : null;
282:
283: final Vocabulary resources = Vocabulary.getResources(null);
284: final String position;
285: if (file != null) {
286: if (line != null) {
287: position = resources.getString(
288: VocabularyKeys.FILE_POSITION_$2, file, line);
289: } else {
290: position = resources.getString(VocabularyKeys.FILE_$1,
291: file);
292: }
293: } else if (line != null) {
294: position = resources
295: .getString(VocabularyKeys.LINE_$1, line);
296: } else {
297: position = null;
298: }
299: if (position != null) {
300: if (message != null) {
301: return position + ": " + message;
302: } else {
303: return position;
304: }
305: } else {
306: return message;
307: }
308: }
309:
310: /**
311: * Closes the reader created by {@link #getReader()}. This method does nothing if
312: * the reader is the {@linkplain #input input} instance given by the user rather
313: * than a reader created by this class from a {@link File} or {@link URL} input.
314: *
315: * @see #closeOnReset
316: */
317: //@Override
318: protected void close() throws IOException {
319: reader = null;
320: super .close();
321: }
322:
323: /**
324: * Service provider interface (SPI) for {@link TextImageReader}s. This
325: * SPI provides a convenient way to control the {@link TextImageReader}
326: * character encoding: the {@link #charset} field. For example, many
327: * {@code Spi} subclasses will put the following line in their
328: * constructor:
329: *
330: * <blockquote><pre>
331: * {@link #charset} = Charset.forName("ISO-LATIN-1"); // ISO Latin Alphabet No. 1 (ISO-8859-1)
332: * </pre></blockquote>
333: *
334: * @since 2.4
335: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/text/TextImageReader.java $
336: * @version $Id: TextImageReader.java 26761 2007-08-29 21:25:45Z desruisseaux $
337: * @author Martin Desruisseaux
338: */
339: public static abstract class Spi extends StreamImageReader.Spi {
340: /**
341: * List of legal input types for {@link TextImageReader}.
342: */
343: private static final Class[] INPUT_TYPES = new Class[] {
344: File.class, URL.class, URLConnection.class,
345: Reader.class, InputStream.class,
346: ImageInputStream.class, String.class // To be interpreted as file path.
347: };
348:
349: /**
350: * Default list of file extensions.
351: */
352: private static final String[] EXTENSIONS = new String[] {
353: "txt", "TXT", "asc", "ASC", "dat", "DAT" };
354:
355: /**
356: * Character encoding, or {@code null} for the default. This field is initially
357: * {@code null}. A value shall be set by subclasses if the files to be decoded
358: * use some specific character encoding.
359: *
360: * @see TextImageReader#getCharset
361: */
362: protected Charset charset;
363:
364: /**
365: * The locale for numbers or dates parsing. For example {@link Locale#US} means that
366: * numbers are expected to use dot as decimal separator. This field is initially
367: * {@code null}, which means that default locale should be used.
368: *
369: * @see TextImageReader#getLineFormat
370: * @see TextRecordImageReader#parseLine
371: */
372: protected Locale locale;
373:
374: /**
375: * The pad value, or {@link Double#NaN} if none. Every occurences of pixel value equals
376: * to this pad value will be replaced by {@link Double#NaN} during read operation. Note
377: * that this replacement doesn't apply to non-pixel values (for example <var>x</var>,
378: * <var>y</var> coordinates in some file format).
379: *
380: * @see TextImageReader#getPadValue
381: * @see TextRecordImageReader#parseLine
382: */
383: protected double padValue;
384:
385: /**
386: * Constructs a quasi-blank {@code TextImageReader.Spi}. It is up to the subclass to
387: * initialize instance variables in order to provide working versions of all methods.
388: * This constructor provides the following defaults:
389: *
390: * <ul>
391: * <li>{@link #inputTypes} = {{@link File}, {@link URL}, {@link URLConnection},
392: * {@link Reader}, {@link InputStream}, {@link ImageInputStream}, {@link String}}</li>
393: *
394: * <li>{@link #suffixes} = {{@code "txt"}, {@code "asc"}, {@code "dat"}}
395: * (lowercases and uppercases)</li>
396: *
397: * <li>{@link #padValue} = {@link Double#NaN}</li>
398: * </ul>
399: *
400: * For efficienty reasons, the above fields are initialized to shared arrays. Subclasses
401: * can assign new arrays, but should not modify the default array content.
402: */
403: public Spi() {
404: inputTypes = INPUT_TYPES;
405: suffixes = EXTENSIONS;
406: padValue = Double.NaN;
407: }
408:
409: /**
410: * Returns {@code true} if the supplied source object appears to be of the format
411: * supported by this reader. The default implementation tries to parse the first
412: * few lines up to 1024 characters.
413: *
414: * @param source The object (typically an {@link ImageInputStream}) to be decoded.
415: * @return {@code true} if the source <em>seems</em> readable.
416: * @throws IOException If an error occured during reading.
417: */
418: public boolean canDecodeInput(final Object source)
419: throws IOException {
420: return canDecodeInput(source, 1024);
421: }
422:
423: /**
424: * Returns {@code true} if the supplied source object appears to be of the format
425: * supported by this reader. The default implementation tries to parse the first
426: * few lines up to the specified number of characters.
427: *
428: * @param source The object (typically an {@link ImageInputStream}) to be decoded.
429: * @param readAheadLimit Maximum number of characters to read. If this amount is reached
430: * but this method still unable to make a choice, then it conservatively returns
431: * {@code false}.
432: * @return {@code true} if the source <em>seems</em> readable.
433: * @throws IOException If an error occured during reading.
434: */
435: protected boolean canDecodeInput(final Object source,
436: final int readAheadLimit) throws IOException {
437: final TestReader test = new TestReader(this );
438: test.setInput(source);
439: final boolean result = test.canDecode(readAheadLimit);
440: test.close();
441: return result;
442: }
443:
444: /**
445: * Returns {@code true} if the content of the first few rows seems valid, or {@code false}
446: * otherwise. The number of rows depends on the row length and the {@code readAheadLimit}
447: * argument given to {@link #canDecodeInput(Object,int) canDecodeInput}.
448: * <p>
449: * The default implementation returns {@code true} if there is at least one row
450: * and every row have the same number of columns.
451: */
452: protected boolean isValidContent(final double[][] rows) {
453: if (rows.length == 0) {
454: return false;
455: }
456: final int length = rows[0].length;
457: for (int i = 1; i < rows.length; i++) {
458: if (rows[i].length != length) {
459: return false;
460: }
461: }
462: return isValidColumnCount(length);
463: }
464:
465: /**
466: * Returns {@code true} if the specified row length is valid. If unsure, this methods
467: * can conservatively returns {@code false}. The default implementation always returns
468: * {@code true}.
469: */
470: boolean isValidColumnCount(final int count) {
471: return true;
472: }
473: }
474: }
|