sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject 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
Date Sat, 30 Jul 2016 17:35:22 GMT
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);
     }
 
     /**



Mime
View raw message