You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2016/07/30 17:35:22 UTC
svn commit: r1754626 - in
/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff:
DeferredEntry.java GeoTiffStore.java ImageFileDirectory.java Reader.java
Author: desruisseaux
Date: Sat Jul 30 17:35:22 2016
New Revision: 1754626
URL: http://svn.apache.org/viewvc?rev=1754626&view=rev
Log:
Complete the parsing of TIFF tags (but not yet the interpretation; the ImageFileDirectory class still needs to be completed).
Added:
sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DeferredEntry.java (with props)
Modified:
sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
Added: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DeferredEntry.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DeferredEntry.java?rev=1754626&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DeferredEntry.java (added)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DeferredEntry.java [UTF-8] Sat Jul 30 17:35:22 2016
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.storage.geotiff;
+
+
+/**
+ * Offset to a TIFF tag entry that has not yet been read.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+final class DeferredEntry implements Comparable<DeferredEntry> {
+ /**
+ * Where to add the entry value after it has been read.
+ */
+ final ImageFileDirectory owner;
+
+ /**
+ * The GeoTIFF tag to decode.
+ */
+ final int tag;
+
+ /**
+ * The GeoTIFF type of the value to read.
+ */
+ final Type type;
+
+ /**
+ * The number of values to read.
+ */
+ final long count;
+
+ /**
+ * Offset from beginning of TIFF file where the values are stored.
+ */
+ final long offset;
+
+ /**
+ * Creates a new deferred entry.
+ */
+ DeferredEntry(final ImageFileDirectory owner, final int tag, final Type type, final long count, final long offset) {
+ this.owner = owner;
+ this.tag = tag;
+ this.type = type;
+ this.count = count;
+ this.offset = offset;
+ }
+
+ /**
+ * Returns +1 if this entry is located after the given entry in the TIFF file,
+ * or -1 if it is located before. A valid TIFF file should not have two entries
+ * at the same location, but it is okay for Apache SIS implementation if such
+ * case nevertheless happen.
+ */
+ @Override
+ public int compareTo(final DeferredEntry other) {
+ return Long.signum(offset - other.offset);
+ }
+}
Propchange: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DeferredEntry.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/DeferredEntry.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java?rev=1754626&r1=1754625&r2=1754626&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java [UTF-8] Sat Jul 30 17:35:22 2016
@@ -51,6 +51,13 @@ public class GeoTiffStore extends DataSt
private Reader reader;
/**
+ * The metadata, or {@code null} if not yet created.
+ *
+ * @see #getMetadata()
+ */
+ private Metadata metadata;
+
+ /**
* Creates a new GeoTIFF store from the given file, URL or stream object.
* This constructor invokes {@link StorageConnector#closeAllExcept(Object)},
* keeping open only the needed resource.
@@ -84,7 +91,18 @@ public class GeoTiffStore extends DataSt
*/
@Override
public synchronized Metadata getMetadata() throws DataStoreException {
- return null;
+ if (metadata == null) try {
+ int index = 0;
+ while (reader.getImageFileDirectory(index) != null) {
+ index++;
+ }
+ metadata = reader.metadata.result();
+ } catch (IOException e) {
+ throw new TIFFException(reader.errors().getString(Errors.Keys.CanNotRead_1, reader.input.filename), e);
+ } catch (ArithmeticException e) {
+ throw new TIFFException(reader.canNotDecode(), e);
+ }
+ return metadata;
}
/**
Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java?rev=1754626&r1=1754625&r2=1754626&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java [UTF-8] Sat Jul 30 17:35:22 2016
@@ -36,11 +36,10 @@ import org.opengis.metadata.citation.Dat
*/
final class ImageFileDirectory {
/**
- * The offset (in bytes since the beginning of the TIFF stream) of the Image File Directory (FID).
- * The directory may be at any location in the file after the header.
- * In particular, an Image File Directory may follow the image that it describes.
+ * {@code true} if this {@code ImageFileDirectory} has not yet read all deferred entries.
+ * When this flag is {@code true}, the {@code ImageFileDirectory} is not yet ready for use.
*/
- final long offset;
+ boolean hasDeferredEntries;
/**
* The size of the image described by this FID, or -1 if the information has not been found.
@@ -70,10 +69,9 @@ final class ImageFileDirectory {
private Compression compression;
/**
- * Creates a new image file directory located at the given offset (in bytes) in the TIFF file.
+ * Creates a new image file directory.
*/
- ImageFileDirectory(final long offset) {
- this.offset = offset;
+ ImageFileDirectory() {
}
/**
@@ -84,7 +82,7 @@ final class ImageFileDirectory {
* @param tag the GeoTIFF tag to decode.
* @param type the GeoTIFF type of the value to read.
* @param count the number of values to read.
- * @return {@code true} on success, or {@code false} for unrecognized value.
+ * @return {@code null} on success, or the unrecognized value otherwise.
* @throws IOException if an error occurred while reading the stream.
* @throws ParseException if the value need to be parsed as date and the parsing failed.
* @throws NumberFormatException if the value need to be parsed as number and the parsing failed.
@@ -92,7 +90,7 @@ final class ImageFileDirectory {
* @throws IllegalArgumentException if a value which was expected to be a singleton is not.
* @throws UnsupportedOperationException if the given type is {@link Type#UNDEFINED}.
*/
- boolean addEntry(final Reader reader, final int tag, final Type type, final long count) throws IOException, ParseException {
+ Object addEntry(final Reader reader, final int tag, final Type type, final long count) throws IOException, ParseException {
switch (tag) {
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -110,7 +108,7 @@ final class ImageFileDirectory {
case Tags.PlanarConfiguration: {
final long value = type.readLong(reader.input, count);
if (value < 1 || value > 2) {
- return false;
+ return value;
}
isPlanar = (value == 2);
break;
@@ -158,7 +156,7 @@ final class ImageFileDirectory {
final long value = type.readLong(reader.input, count);
compression = Compression.valueOf(value);
if (compression == null) {
- return false;
+ return value;
}
break;
}
@@ -439,6 +437,6 @@ final class ImageFileDirectory {
break;
}
}
- return true;
+ return null;
}
}
Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java?rev=1754626&r1=1754625&r2=1754626&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java [UTF-8] Sat Jul 30 17:35:22 2016
@@ -18,6 +18,8 @@ package org.apache.sis.storage.geotiff;
import java.util.List;
import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Iterator;
import java.io.IOException;
import java.nio.ByteOrder;
import java.text.ParseException;
@@ -74,12 +76,28 @@ final class Reader extends GeoTIFF {
private final byte intSizeExpansion;
/**
+ * Offset (relative to the beginning of the TIFF file) of the next Image File Directory (IFD)
+ * to read, or 0 if we have finished to read all of them.
+ */
+ private long nextIFD;
+
+ /**
* Positions of each <cite>Image File Directory</cite> (IFD) in this file.
* Those positions are fetched when first needed.
*/
private final List<ImageFileDirectory> imageFileDirectories = new ArrayList<>();
/**
+ * Entries having a value that can not be read immediately, but instead have a pointer
+ * to a value stored elsewhere in the file. Those values will be read only when needed.
+ *
+ * <div class="note"><b>Implementation note:</b>
+ * we use a {@code LinkedList} because we will perform frequent additions and removals,
+ * but no random access.</div>
+ */
+ private final LinkedList<DeferredEntry> deferredEntries = new LinkedList<>();
+
+ /**
* Builder for the metadata.
*/
final MetadataBuilder metadata;
@@ -116,6 +134,7 @@ final class Reader extends GeoTIFF {
switch (input.readShort()) {
case 42: { // Magic number of classical format.
intSizeExpansion = 0;
+ nextIFD = input.readUnsignedInt();
return;
}
case 43: { // Magic number of BigTIFF format.
@@ -129,6 +148,7 @@ final class Reader extends GeoTIFF {
* one result in the end, but we did that generic computation anyway for keeping the
* code almost ready if the BigTIFF specification adds support for 16 bytes pointer.
*/
+ nextIFD = readUnsignedInt();
return;
}
}
@@ -136,13 +156,13 @@ final class Reader extends GeoTIFF {
}
}
// Do not invoke errors() yet because GeoTiffStore construction may not be finished.
- throw new DataStoreException(Errors.format(Errors.Keys.UnexpectedFileFormat_2, "TIFF", input.filename));
+ throw new TIFFException(Errors.format(Errors.Keys.UnexpectedFileFormat_2, "TIFF", input.filename));
}
/**
* Returns a default message for parsing error.
*/
- private String canNotRead() {
+ final String canNotDecode() {
return errors().getString(Errors.Keys.CanNotParseFile_2, "TIFF", input.filename);
}
@@ -160,7 +180,7 @@ final class Reader extends GeoTIFF {
if (pointer >= 0) {
return pointer;
}
- throw new DataStoreException(canNotRead());
+ throw new TIFFException(canNotDecode());
}
/**
@@ -177,47 +197,46 @@ final class Reader extends GeoTIFF {
if (entry >= 0) {
return entry;
}
- throw new DataStoreException(canNotRead());
+ throw new TIFFException(canNotDecode());
}
/**
- * Reads the next bytes in the {@linkplain #input}, which must be the 32 or 64 bits
- * offset to the next <cite>Image File Directory</cite> (IFD), then parses that IFD.
- * The IFD consists of a 2 (classical) or 8 (BigTiff)-bytes count of the number of
- * directory entries, followed by a sequence of 12-byte field entries, followed by
- * a pointer to the next IFD (or 0 if none).
+ * Returns the <cite>Image File Directory</cite> (IFD) at the given index.
+ * If the IFD has already been read, then it is returned.
+ * Otherwise this method reads the IFD now and returns it.
*
- * <p>The parsed entry is added to the {@link #imageFileDirectories} list.</p>
+ * <p>The IFD consists of a 2 (classical) or 8 (BigTiff)-bytes count of the number of directory entries,
+ * followed by a sequence of 12-byte field entries, followed by a pointer to the next IFD (or 0 if none).</p>
*
- * @return {@code true} if we found a new IFD, or {@code false}�if there is no more IFD.
- * @throws ArithmeticException if the pointer to the next IFD is too far.
+ * @return the IFD if we found it, or {@code null}�if there is no more IFD at the given index.
+ * @throws ArithmeticException if the pointer to a next IFD is too far.
*/
- private boolean nextImageFileDirectory() throws IOException, DataStoreException {
- final long offset = readUnsignedInt();
- if (offset == 0) {
- return false;
- }
- /*
- * Design note: we parse the Image File Directory entry now because even if we were
- * not interrested in that IFD, we need to go anyway after its last record in order
- * to get the pointer to the next IFD.
- */
- input.seek(Math.addExact(origin, offset));
- final int offsetSize = Integer.BYTES << intSizeExpansion;
- final ImageFileDirectory dir = new ImageFileDirectory(offset);
- for (long remaining = readUnsignedShort(); --remaining >= 0;) {
+ final ImageFileDirectory getImageFileDirectory(final int index) throws IOException, DataStoreException {
+ while (index >= imageFileDirectories.size()) {
+ if (nextIFD == 0) {
+ return null;
+ }
+ input.seek(Math.addExact(origin, nextIFD));
+ nextIFD = 0; // Prevent trying other IFD if we fail to read this one.
/*
- * Each entry in the Image File Directory has the following format:
- * - The tag that identifies the field (see constants in the Tags class).
- * - The field type (see constants inherited from the GeoTIFF class).
- * - The number of values of the indicated type.
- * - The value, or the file offset to the value elswhere in the file.
+ * Design note: we parse the Image File Directory entry now because even if we were
+ * not interrested in that IFD, we need to go anyway after its last record in order
+ * to get the pointer to the next IFD.
*/
- final int tag = input.readUnsignedShort();
- final Type type = Type.valueOf(input.readShort()); // May be null.
- final long count = readUnsignedInt();
- try {
- final long size = (type != null) ? Math.multiplyExact(type.size, count) : 0;
+ final int offsetSize = Integer.BYTES << intSizeExpansion;
+ final ImageFileDirectory dir = new ImageFileDirectory();
+ for (long remaining = readUnsignedShort(); --remaining >= 0;) {
+ /*
+ * Each entry in the Image File Directory has the following format:
+ * - The tag that identifies the field (see constants in the Tags class).
+ * - The field type (see constants inherited from the GeoTIFF class).
+ * - The number of values of the indicated type.
+ * - The value, or the file offset to the value elswhere in the file.
+ */
+ final int tag = input.readUnsignedShort();
+ final Type type = Type.valueOf(input.readShort()); // May be null.
+ final long count = readUnsignedInt();
+ final long size = (type != null) ? Math.multiplyExact(type.size, count) : 0;
if (size <= offsetSize) {
/*
* If the value can fit inside the number of bytes given by 'offsetSize', then the value is
@@ -225,24 +244,93 @@ final class Reader extends GeoTIFF {
*/
final long position = input.getStreamPosition();
if (size != 0) {
- /*
- * A size of zero means that we have an unknown type, in which case the TIFF specification
- * recommends to ignore it (for allowing them to add new types in the future), or an entry
- * without value (count = 0) - in principle illegal but we make this reader tolerant.
- */
- dir.addEntry(this, tag, type, count);
+ Object error;
+ try {
+ /*
+ * A size of zero means that we have an unknown type, in which case the TIFF specification
+ * recommends to ignore it (for allowing them to add new types in the future), or an entry
+ * without value (count = 0) - in principle illegal but we make this reader tolerant.
+ */
+ error = dir.addEntry(this, tag, type, count);
+ } catch (ParseException | RuntimeException e) {
+ error = e;
+ }
+ if (error != null) {
+ warning(tag, error);
+ }
}
- input.seek(position + offsetSize);
+ input.seek(position + offsetSize); // Usually just move the buffer position by a few bytes.
} else {
- // offset from beginning of file where the values are stored.
- final long value = readUnsignedInt();
- // TODO
+ // Offset from beginning of TIFF file where the values are stored.
+ deferredEntries.add(new DeferredEntry(dir, tag, type, count, readUnsignedInt()));
+ dir.hasDeferredEntries = true;
}
- } catch (IOException | ParseException | RuntimeException e) {
- owner.warning(errors().getString(Errors.Keys.CanNotSetPropertyValue_1, Tags.name(tag)), e);
}
+ imageFileDirectories.add(dir);
+ nextIFD = readUnsignedInt(); // Zero if the IFD that we just read was the last one.
+ }
+ /*
+ * At this point we got the requested IFD. But maybe some deferred entries need to be read.
+ * The values of those entries may be anywhere in the TIFF file, in any order. Given that
+ * seek operations in the input stream may be costly or even not possible, we try to read
+ * all values in sequential order, including values of other IFD if there is some before
+ * our IFD of interest.
+ */
+ final ImageFileDirectory dir = imageFileDirectories.get(index);
+ if (dir.hasDeferredEntries) {
+ deferredEntries.sort(null); // Sequential order in input stream.
+ final long ignoreBefore = input.getStreamPosition() - origin; // Avoid seeking back, unless we need to.
+ DeferredEntry stopAfter = null; // Avoid reading more entries than needed.
+ Iterator<DeferredEntry> it = deferredEntries.descendingIterator();
+ while (it.hasNext()) {
+ stopAfter = it.next();
+ if (stopAfter.owner == dir) break;
+ }
+ it = deferredEntries.iterator();
+ while (it.hasNext()) {
+ final DeferredEntry entry = it.next();
+ if (entry.owner == dir || entry.offset >= ignoreBefore) {
+ input.seek(Math.addExact(origin, entry.offset));
+ Object error;
+ try {
+ error = entry.owner.addEntry(this, entry.tag, entry.type, entry.count);
+ } catch (ParseException | RuntimeException e) {
+ error = e;
+ }
+ if (error != null) {
+ warning(entry.tag, error);
+ }
+ it.remove(); // Remove only on success, but before we try to read other entries.
+ }
+ if (entry == stopAfter) break;
+ }
+ dir.hasDeferredEntries = false;
+ }
+ return dir;
+ }
+
+ /**
+ * Logs a warning about a tag that can not be read, but does not interrupt the TIFF reading.
+ *
+ * @param tag the tag than can not be read.
+ * @param error the value than can not be understand, or the exception that we got while trying to parse it.
+ */
+ private void warning(final int tag, final Object error) {
+ final short key;
+ final Object[] args;
+ final Exception exception;
+ if (error instanceof Exception) {
+ key = Errors.Keys.CanNotSetPropertyValue_1;
+ args = new Object[1];
+ exception = (Exception) error;
+ } else {
+ key = Errors.Keys.UnknownEnumValue_2;
+ args = new Object[2];
+ args[1] = error;
+ exception = null;
}
- return imageFileDirectories.add(dir);
+ args[0] = Tags.name(tag);
+ owner.warning(errors().getString(key, args), exception);
}
/**