sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1754039 - in /sis/branches/JDK8: core/sis-utility/src/main/java/org/apache/sis/setup/ storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ storage/sis-storage/src/main/java/org/apache/sis/internal/storage/ storage/sis-storage/...
Date Mon, 25 Jul 2016 17:53:56 GMT
Author: desruisseaux
Date: Mon Jul 25 17:53:56 2016
New Revision: 1754039

URL: http://svn.apache.org/viewvc?rev=1754039&view=rev
Log:
Parse more GeoTIFF tags. Contains a first mapping from TIFF to ISO 19115.

Added:
    sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/TIFFException.java
      - copied, changed from r1754038, sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/InvalidTiffHeaderException.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
      - copied, changed from r1754038, sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java
Removed:
    sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/InvalidTiffHeaderException.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java
Modified:
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java
    sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java
    sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java
    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
    sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
    sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java?rev=1754039&r1=1754038&r2=1754039&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/setup/OptionKey.java [UTF-8] Mon Jul 25 17:53:56 2016
@@ -78,8 +78,8 @@ public class OptionKey<T> implements Ser
      * For example this option can be used when reading plain text files, but is ignored when
      * reading XML files having a {@code <?xml version="1.0" encoding="…"?>} declaration.
      *
-     * <p>If this option is not provided, then the default value is the
-     * {@link Charset#defaultCharset() platform default}.</p>
+     * <p>If this option is not provided, then the default value is format specific.
+     * That default is often, but not necessarily, the {@link Charset#defaultCharset() platform default}.</p>
      *
      * @since 0.4
      */
@@ -160,8 +160,8 @@ public class OptionKey<T> implements Ser
     /**
      * Creates a new key of the given name for values of the given type.
      *
-     * @param name The key name.
-     * @param type The type of values.
+     * @param name  the key name.
+     * @param type  the type of values.
      */
     protected OptionKey(final String name, final Class<T> type) {
         ArgumentChecks.ensureNonEmpty("name", name);
@@ -173,7 +173,7 @@ public class OptionKey<T> implements Ser
     /**
      * Returns the name of this option key.
      *
-     * @return The name of this option key.
+     * @return the name of this option key.
      */
     public String getName() {
         return name;
@@ -182,7 +182,7 @@ public class OptionKey<T> implements Ser
     /**
      * Returns the type of values associated to this option key.
      *
-     * @return The type of values.
+     * @return the type of values.
      */
     public final Class<T> getElementType() {
         return type;
@@ -199,8 +199,8 @@ public class OptionKey<T> implements Ser
      *     }
      * }
      *
-     * @param  options The map where to search for the value, or {@code null} if not yet created.
-     * @return The current value in the map for the this option, or {@code null} if none.
+     * @param  options  the map where to search for the value, or {@code null} if not yet created.
+     * @return the current value in the map for the this option, or {@code null} if none.
      */
     public T getValueFrom(final Map<OptionKey<?>,?> options) {
         return (options != null) ? type.cast(options.get(this)) : null;
@@ -217,9 +217,9 @@ public class OptionKey<T> implements Ser
      *     }
      * }
      *
-     * @param  options The map where to set the value, or {@code null} if not yet created.
-     * @param  value   The new value for the given option, or {@code null} for removing the value.
-     * @return The given map of options, or a new map if the given map was null. The returned value
+     * @param  options  the map where to set the value, or {@code null} if not yet created.
+     * @param  value    the new value for the given option, or {@code null} for removing the value.
+     * @return the given map of options, or a new map if the given map was null. The returned value
      *         may be null if the given map and the given value are both null.
      */
     public Map<OptionKey<?>,Object> setValueInto(Map<OptionKey<?>,Object> options, final T value) {
@@ -237,7 +237,7 @@ public class OptionKey<T> implements Ser
     /**
      * Returns {@code true} if the given object is an instance of the same class having the same name and type.
      *
-     * @param object The object to compare with this {@code OptionKey} for equality.
+     * @param object  the object to compare with this {@code OptionKey} for equality.
      */
     @Override
     public boolean equals(final Object object) {
@@ -272,7 +272,7 @@ public class OptionKey<T> implements Ser
      * Resolves this option key on deserialization. This method is invoked
      * only for instance of the exact {@code OptionKey} class, not subclasses.
      *
-     * @return The unique {@code OptionKey} instance.
+     * @return  the unique {@code OptionKey} instance.
      */
     private Object readResolve() {
         try {

Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java?rev=1754039&r1=1754038&r2=1754039&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Compression.java [UTF-8] Mon Jul 25 17:53:56 2016
@@ -21,6 +21,13 @@ package org.apache.sis.storage.geotiff;
  * Possible values for {@link Tags#Compression}.
  * Data compression applies only to raster image data. All other TIFF fields are unaffected.
  *
+ * <p>Except otherwise noted, field names in this class are upper-case variant of the names
+ * used in Coverage Web Services (CSW) as specified in the following specification:</p>
+ *
+ * <blockquote>OGC 12-100: GML Application Schema - Coverages - GeoTIFF Coverage Encoding Profile</blockquote>
+ *
+ * The main exception is {@link #CCITT}, which has different name in CSW query and response.
+ *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8
@@ -31,41 +38,81 @@ enum Compression {
     /**
      * No compression, but pack data into bytes as tightly as possible, leaving no unused bits except
      * potentially at the end of rows. The component values are stored as an array of type byte.
+     * <ul>
+     *   <li>Name in CSW query:    "None"</li>
+     *   <li>Name in CSW response: "None"</li>
+     * </ul>
      */
     NONE(1),
 
     /**
      * CCITT Group 3, 1-Dimensional Modified Huffman run length encoding.
+     * <ul>
+     *   <li>Name in CSW query:    "Huffman"</li>
+     *   <li>Name in CSW response: "CCITTRLE"</li>
+     * </ul>
      */
-    CCITT(2),
+    CCITTRLE(2),
 
     /**
      * PackBits compression, a simple byte-oriented run length scheme.
+     * <ul>
+     *   <li>Name in CSW query:    "PackBits"</li>
+     *   <li>Name in CSW response: "PackBits"</li>
+     * </ul>
      */
-    PACK_BITS(32773),
+    PACKBITS(32773),
 
-    // ---- End of baseline GeoTIFF. Remaining are extensions ----
+    // ---- End of baseline GeoTIFF. Remaining are extensions cited by OGC standard ----
 
     /**
      * LZW compression.
+     * <ul>
+     *   <li>Name in CSW query:    "LZW"</li>
+     *   <li>Name in CSW response: "LZW"</li>
+     * </ul>
      */
     LZW(5),
 
     /**
-     * Deflate compression, like ZIP format.
+     * Deflate compression, like ZIP format. This is sometime named {@code "ADOBE_DEFLATE"},
+     * withe the {@code "DEFLATE"} name used for another compression method with code 32946.
+     * <ul>
+     *   <li>Name in CSW query:    "Deflate"</li>
+     *   <li>Name in CSW response: "Deflate"</li>
+     *   <li>Other name:           "ADOBE_DEFLATE"</li>
+     * </ul>
      */
     DEFLATE(8),
 
     /**
      * JPEG compression.
-     */
-    JPEG(6),
-
-    /**
-     * JPEG compression.
-     * @todo what is the difference with JPEG?
-     */
-    JPEG_2(7);
+     * <ul>
+     *   <li>Name in CSW query:    "JPEG"</li>
+     *   <li>Name in CSW response: "JPEG"</li>
+     *   <li>Name of old JPEG:     "OJPEG" (code 6)</li>
+     * </ul>
+     */
+    JPEG(7),
+
+    // ---- Remaining are extension to both baseline and OGC standard ----
+
+    /** Unsupported. */ CCITTFAX3(3),
+    /** Unsupported. */ CCITTFAX4(4),
+    /** Unsupported. */ NEXT(32766),
+    /** Unsupported. */ CCITTRLEW(32771),
+    /** Unsupported. */ THUNDERSCAN(32809),
+    /** Unsupported. */ IT8CTPAD(32895),
+    /** Unsupported. */ IT8LW(32896),
+    /** Unsupported. */ IT8MP(32897),
+    /** Unsupported. */ IT8BL(32898),
+    /** Unsupported. */ PIXARFILM(32908),
+    /** Unsupported. */ PIXARLOG(32909),
+    /** Unsupported. */ DCS(32947),
+    /** Unsupported. */ JBIG(34661),
+    /** Unsupported. */ SGILOG(34676),
+    /** Unsupported. */ SGILOG24(34677),
+    /** Unsupported. */ JP2000(34712);
 
     /**
      * The TIFF code for this compression.
@@ -86,12 +133,20 @@ enum Compression {
         if ((code & ~0xFFFF) == 0) {                // Should be a short according TIFF specification.
             switch ((int) code) {
                 case 1:     return NONE;
-                case 2:     return CCITT;
+                case 2:     return CCITTRLE;
                 case 5:     return LZW;
-                case 6:     return JPEG;
-                case 7:     return JPEG_2;
-                case 8:     return DEFLATE;
-                case 32773: return PACK_BITS;
+                case 6:     // "old-style" JPEG, later overriden in Technical Notes 2.
+                case 7:     return JPEG;
+                case 8:
+                case 32946: return DEFLATE;
+                case 32773: return PACKBITS;
+                default: {
+                    // Fallback for uncommon formats.
+                    for (final Compression c : values()) {
+                        if (c.code == code) return c;
+                    }
+                    break;
+                }
             }
         }
         return null;

Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java?rev=1754039&r1=1754038&r2=1754039&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java [UTF-8] Mon Jul 25 17:53:56 2016
@@ -16,8 +16,9 @@
  */
 package org.apache.sis.storage.geotiff;
 
-import java.util.Locale;
 import java.io.Closeable;
+import org.apache.sis.util.Localized;
+import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -35,12 +36,19 @@ abstract class GeoTIFF implements Closea
     /**
      * The locale for formatting error messages.
      */
-    final Locale locale;
+    private final Localized owner;
 
     /**
      * For subclass constructors.
      */
-    GeoTIFF(final Locale locale) {
-        this.locale = locale;
+    GeoTIFF(final Localized owner) {
+        this.owner = owner;
+    }
+
+    /**
+     * Returns the resources to use for formatting error messages.
+     */
+    final Errors errors() {
+        return Errors.getResources(owner.getLocale());
     }
 }

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=1754039&r1=1754038&r2=1754039&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] Mon Jul 25 17:53:56 2016
@@ -18,10 +18,11 @@ package org.apache.sis.storage.geotiff;
 
 import java.io.IOException;
 import org.opengis.metadata.Metadata;
-import org.apache.sis.internal.storage.ChannelDataInput;
+import org.apache.sis.setup.OptionKey;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.StorageConnector;
+import org.apache.sis.internal.storage.ChannelDataInput;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.Classes;
@@ -59,7 +60,7 @@ public class GeoTiffStore extends DataSt
         }
         storage.closeAllExcept(input);
         try {
-            reader = new Reader(input, super.getLocale());
+            reader = new Reader(input, storage.getOption(OptionKey.ENCODING), this);
         } catch (IOException e) {
             throw new DataStoreException(e);
         }

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=1754039&r1=1754038&r2=1754039&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] Mon Jul 25 17:53:56 2016
@@ -17,7 +17,6 @@
 package org.apache.sis.storage.geotiff;
 
 import java.io.IOException;
-import org.apache.sis.internal.storage.ChannelDataInput;
 
 
 /**
@@ -54,6 +53,13 @@ final class ImageFileDirectory {
     private int samplesPerPixel;
 
     /**
+     * If {@code true}, the components are stored in separate “component planes”.
+     * The default is {@code false}, which stands for the "chunky" format
+     * (for example RGB data stored as RGBRGBRGB).
+     */
+    private boolean isPlanar;
+
+    /**
      * The compression method, or {@code null} if unknown. If the compression method is unknown
      * or unsupported we can not read the image, but we still can read the metadata.
      */
@@ -69,26 +75,109 @@ final class ImageFileDirectory {
     /**
      * Adds the value read from the current position in the given stream
      * for the entry identified by the given GeoTIFF tag.
+     *
+     * @param  reader   the input stream to read, the metadata and the character encoding.
+     * @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.
+     * @throws IOException if an error occurred while reading the stream.
+     * @throws NumberFormatException if the value was stored in ASCII and can not be parsed.
+     * @throws ArithmeticException if the value can not be represented in the expected Java type.
+     * @throws IllegalArgumentException if a value which was expected to be a singleton is not.
+     * @throws UnsupportedOperationException if the given type is {@link Type#UNDEFINED}.
      */
-    void addEntry(final ChannelDataInput input, final int tag, final Type type, final long count) throws IOException {
+    boolean addEntry(final Reader reader, final int tag, final Type type, final long count) throws IOException {
         switch (tag) {
-            case Tags.PhotometricInterpretation: {
-                final boolean blackIsZero = (type.readLong(input, count) != 0);
+            /*
+             * Person who created the image. Some older TIFF files used this tag for storing
+             * Copyright information, but Apache SIS does not support this legacy practice.
+             */
+            case Tags.Artist: {
+                for (final String value : type.readString(reader.input, count, reader.encoding)) {
+                    reader.metadata.addAuthorName(value);
+                }
+                break;
+            }
+            /*
+             * Number of bits per component. The array length should be the number of components in a
+             * pixel (e.g. 3 for RGB values). Typically, all components have the same number of bits.
+             * But the TIFF specification allows different values.
+             */
+            case Tags.BitsPerSample: {
+                // TODO
+                break;
+            }
+            /*
+             * The height of the dithering or halftoning matrix used to create a dithered or halftoned
+             * bilevel file. Meaningful only if Threshholding = 2.
+             */
+            case Tags.CellLength: {
+                // TODO
+                break;
+            }
+            /*
+             * The width of the dithering or halftoning matrix used to create a dithered or halftoned
+             * bilevel file. Meaningful only if Threshholding = 2.
+             */
+            case Tags.CellWidth: {
                 // TODO
                 break;
             }
+            /*
+             * The Red-Green-Blue lookup table map for palette-color images (IndexColorModel in Java2D).
+             * All the Red values come first, followed by the Green values, then the Blue values.
+             * The number of values for each color is (1 << BitsPerSample) and the type of each value is USHORT.
+             * 0 represents the minimum intensity (black is 0,0,0) and 65535 represents the maximum intensity.
+             * If the Indexed tag has value 1 and PhotometricInterpretation is different from PaletteColor,
+             * then the color space may be different than RGB.
+             */
+            case Tags.ColorMap: {
+                // TODO
+                break;
+            }
+            /*
+             * Compression scheme used on the image data.
+             */
             case Tags.Compression: {
-                compression = Compression.valueOf(type.readLong(input, count));
+                final long value = type.readLong(reader.input, count);
+                compression = Compression.valueOf(value);
+                if (compression == null) {
+                    return false;
+                }
+                break;
+            }
+            /*
+             * Copyright notice of the person or organization that claims the copyright to the image.
+             */
+            case Tags.Copyright: {
+                for (final String value : type.readString(reader.input, count, reader.encoding)) {
+                    reader.metadata.parseLegalNotice(value);
+                }
+                break;
+            }
+            case Tags.PlanarConfiguration: {
+                final long value = type.readLong(reader.input, count);
+                if (value < 1 || value > 2) {
+                    return false;
+                }
+                isPlanar = (value == 2);
+                break;
+            }
+            case Tags.PhotometricInterpretation: {
+                final boolean blackIsZero = (type.readLong(reader.input, count) != 0);
+                // TODO
                 break;
             }
             case Tags.ImageLength: {
-                imageHeight = type.readUnsignedLong(input, count);
+                imageHeight = type.readUnsignedLong(reader.input, count);
                 break;
             }
             case Tags.ImageWidth: {
-                imageWidth = type.readUnsignedLong(input, count);
+                imageWidth = type.readUnsignedLong(reader.input, count);
                 break;
             }
         }
+        return true;
     }
 }

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=1754039&r1=1754038&r2=1754039&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] Mon Jul 25 17:53:56 2016
@@ -18,12 +18,15 @@ package org.apache.sis.storage.geotiff;
 
 import java.util.List;
 import java.util.ArrayList;
-import java.util.Locale;
 import java.io.IOException;
 import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import org.apache.sis.internal.storage.ChannelDataInput;
+import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.Localized;
 
 
 /**
@@ -49,7 +52,13 @@ final class Reader extends GeoTIFF {
     /**
      * The stream from which to read the data.
      */
-    private final ChannelDataInput input;
+    final ChannelDataInput input;
+
+    /**
+     * The encoding of strings in the metadata. The string specification said that is shall be US-ASCII,
+     * but Apache SIS nevertheless let the user specifies an alternative encoding if needed.
+     */
+    final Charset encoding;
 
     /**
      * Stream position of the first byte of the GeoTIFF file. This is usually zero.
@@ -73,22 +82,31 @@ final class Reader extends GeoTIFF {
     private final byte intSizeExpansion;
 
     /**
-     * Positions of each <cite>Image File Directory</cite> (IFD) in this file. Those positions are fetched
-     * when first needed.
+     * 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<>();
 
     /**
+     * Builder for the metadata.
+     */
+    final MetadataBuilder metadata;
+
+    /**
      * Creates a new GeoTIFF reader which will read data from the given input.
      * The input must be at the beginning of the GeoTIFF file.
      *
      * @throws IOException if an error occurred while reading bytes from the stream.
      * @throws DataStoreException if the file is not encoded in the TIFF or BigTIFF format.
      */
-    Reader(final ChannelDataInput input, final Locale locale) throws IOException, DataStoreException {
-        super(locale);
+    Reader(final ChannelDataInput input, final Charset encoding, final Localized owner)
+            throws IOException, DataStoreException
+    {
+        super(owner);
         this.input = input;
         origin = input.getStreamPosition();
+        metadata = new MetadataBuilder();
+        this.encoding = (encoding != null) ? encoding : StandardCharsets.US_ASCII;
         /*
          * A TIFF file begins with either "II" (0x4949) or "MM" (0x4D4D) characters.
          * Those characters identify the byte order. Note we we do not need to care
@@ -128,15 +146,15 @@ final class Reader extends GeoTIFF {
                 }
             }
         }
-        throw new DataStoreException(Errors.getResources(locale).getString(
-                Errors.Keys.UnexpectedFileFormat_2, "TIFF", input.filename));
+        // Do not invoke errors() yet because GeoTiffStore construction may not be finished.
+        throw new DataStoreException(Errors.format(Errors.Keys.UnexpectedFileFormat_2, "TIFF", input.filename));
     }
 
     /**
      * Returns a default message for parsing error.
      */
     private String canNotRead() {
-        return Errors.getResources(locale).getString(Errors.Keys.CanNotParseFile_2, "TIFF", input.filename);
+        return errors().getString(Errors.Keys.CanNotParseFile_2, "TIFF", input.filename);
     }
 
     /**
@@ -222,7 +240,7 @@ final class Reader extends GeoTIFF {
                      * 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(input, tag, type, count);
+                    dir.addEntry(this, tag, type, count);
                 }
                 input.seek(position + offsetSize);
             } else {

Copied: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/TIFFException.java (from r1754038, sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/InvalidTiffHeaderException.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/TIFFException.java?p2=sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/TIFFException.java&p1=sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/InvalidTiffHeaderException.java&r1=1754038&r2=1754039&rev=1754039&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/InvalidTiffHeaderException.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/TIFFException.java [UTF-8] Mon Jul 25 17:53:56 2016
@@ -16,39 +16,39 @@
  */
 package org.apache.sis.storage.geotiff;
 
-import java.io.IOException;
+import org.apache.sis.storage.DataStoreException;
 
 
 /**
- * Thrown when a TIFF Image File Directory can not be read because of a logical inconsistency.
+ * Thrown when a TIFF file can not be read because of a logical inconsistency.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8
  * @version 0.8
  * @module
  */
-class InvalidTiffHeaderException extends IOException {
+public class TIFFException extends DataStoreException {
     /**
      * For cross-version compatibility.
      */
     private static final long serialVersionUID = 3469934460013440211L;
 
     /**
-     * Creates a new exception with the given error message.
+     * Creates an exception with the specified details message.
      *
-     * @param message the error message.
+     * @param message  the detail message.
      */
-    InvalidTiffHeaderException(String message) {
+    public TIFFException(String message) {
         super(message);
     }
 
     /**
-     * Creates a new exception with the given error message and error cause.
+     * Creates an exception with the specified details message and cause.
      *
-     * @param message  the error message.
-     * @param cause    the cause of this error.
+     * @param message  the detail message.
+     * @param cause    the cause for this exception.
      */
-    InvalidTiffHeaderException(String message, final Throwable cause) {
+    public TIFFException(String message, final Throwable cause) {
         super(message, cause);
     }
 }

Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java?rev=1754039&r1=1754038&r2=1754039&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java [UTF-8] Mon Jul 25 17:53:56 2016
@@ -157,7 +157,16 @@ enum Type {
             if (value >= 0) {
                 return value;
             }
-            return super.readLong(input, count);
+            throw new ArithmeticException(canNotConvert(Long.toUnsignedString(value)));
+        }
+
+        @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
+            ensureSingleton(count);
+            final long value = input.readLong();
+            if (value >= 0) {
+                return value;
+            }
+            return Double.parseDouble(Long.toUnsignedString(value));    // Inefficient but should be very rare.
         }
     },
 
@@ -176,7 +185,7 @@ enum Type {
             if (r == value) {
                 return r;
             }
-            return super.readLong(input, count);            // Throws the exception.
+            throw new ArithmeticException(canNotConvert(Float.toString(value)));
         }
 
         @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
@@ -200,7 +209,7 @@ enum Type {
             if (r == value) {
                 return r;
             }
-            return super.readLong(input, count);            // Throws the exception.
+            throw new ArithmeticException(canNotConvert(Double.toString(value)));
         }
 
         @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
@@ -224,7 +233,7 @@ enum Type {
             if ((numerator % denominator) == 0) {
                 return numerator / denominator;
             }
-            return super.readLong(input, count);            // Throws the exception.
+            throw new ArithmeticException(canNotConvert(toString(numerator, denominator)));
         }
 
         @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
@@ -235,7 +244,7 @@ enum Type {
         @Override String[] readString(final ChannelDataInput input, final long length, final Charset charset) throws IOException {
             ensureSingleton(length);
             return new String[] {
-                new StringBuilder().append(input.readInt()).append('/').append(input.readInt()).toString()
+                toString(input.readInt(), input.readInt())
             };
         }
     },
@@ -255,7 +264,7 @@ enum Type {
             if ((numerator % denominator) == 0) {
                 return numerator / denominator;
             }
-            return super.readLong(input, count);            // Throws the exception.
+            throw new ArithmeticException(canNotConvert(toString(numerator, denominator)));
         }
 
         @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
@@ -266,7 +275,7 @@ enum Type {
         @Override String[] readString(final ChannelDataInput input, final long length, final Charset charset) throws IOException {
             ensureSingleton(length);
             return new String[] {
-                new StringBuilder().append(input.readUnsignedInt()).append('/').append(input.readUnsignedInt()).toString()
+                toString(input.readUnsignedInt(), input.readUnsignedInt())
             };
         }
     },
@@ -299,21 +308,13 @@ enum Type {
         @Override long readLong(final ChannelDataInput input, final long count) throws IOException {
             final String[] lines = readString(input, count, StandardCharsets.US_ASCII);
             ensureSingleton(lines.length);
-            try {
-                return Long.parseLong(lines[0]);
-            } catch (NumberFormatException e) {
-                throw new InvalidTiffHeaderException(unparsable(lines), e);
-            }
+            return Long.parseLong(lines[0]);
         }
 
         @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
             final String[] lines = readString(input, count, StandardCharsets.US_ASCII);
             ensureSingleton(lines.length);
-            try {
-                return Double.parseDouble(lines[0]);
-            } catch (NumberFormatException e) {
-                throw new InvalidTiffHeaderException(unparsable(lines), e);
-            }
+            return Double.parseDouble(lines[0]);
         }
     };
 
@@ -358,11 +359,10 @@ enum Type {
     }
 
     /**
-     * Returns the error message for unparsable number.
+     * Formats a rational number. This is a helper method for {@link #RATIONAL} and {@link #URATIONAL} types.
      */
-    static String unparsable(final String[] lines) {
-        // TODO: replace "<?>" by the actual tag name.
-        return Errors.format(Errors.Keys.UnparsableStringInElement_2, "<?>", lines[0]);
+    static String toString(final long numerator, final long denominator) {
+        return new StringBuilder().append(numerator).append('/').append(denominator).toString();
     }
 
     /**
@@ -371,26 +371,38 @@ enum Type {
      * are treated differently.
      *
      * @param  count  the number of values to read.
-     * @throws InvalidTiffHeaderException if {@code count} does not have the expected value.
+     * @throws IllegalArgumentException if {@code count} does not have the expected value.
      */
-    static void ensureSingleton(final long count) throws IOException {
+    static void ensureSingleton(final long count) {
         if (count != 1) {
-            // TODO: replace "<?>" by the actual tag name.
-            throw new InvalidTiffHeaderException(Errors.format(Errors.Keys.TooManyOccurrences_2, 1, "<?>"));
+            // Even if the methods did not expected an array in argument, we are conceptually reading an array.
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, 1, count));
         }
     }
 
     /**
+     * Formats an error message for a value that can not be converted.
+     */
+    final String canNotConvert(final String value) {
+        return Errors.format(Errors.Keys.CanNotConvertValue_2, value, name());
+    }
+
+    /**
      * Reads a single value which is expected to be positive. A negative value may be an encoding error in the
      * big TIFF file, or if it was really the intended value then something greater than what we can support.
+     *
+     * @throws IOException if an error occurred while reading the stream.
+     * @throws NumberFormatException if the value was stored in ASCII and can not be parsed.
+     * @throws ArithmeticException if the value can not be represented in the Java signed {@code long} type.
+     * @throws IllegalArgumentException if the value is not a singleton.
+     * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
     final long readUnsignedLong(final ChannelDataInput input, final long count) throws IOException {
         final long value = readLong(input, count);
         if (value >= 0) {
             return value;
         }
-        // TODO: replace "<?>" by the actual tag name.
-        throw new InvalidTiffHeaderException(Errors.format(Errors.Keys.UnexpectedValueInElement_2, "<?>", value));
+        throw new ArithmeticException(canNotConvert(Long.toUnsignedString(value)));
     }
 
     /**
@@ -404,12 +416,14 @@ enum Type {
      * @param  input  the input from where to read the value.
      * @param  count  the amount of values (normally exactly 1).
      * @return the value as a {@code long}.
-     * @throws InvalidTiffHeaderException if the value can not be converted to a {@code long}.
-     * @throws IOException if another error occurred while reading the stream.
+     * @throws IOException if an error occurred while reading the stream.
+     * @throws NumberFormatException if the value was stored in ASCII and can not be parsed.
+     * @throws ArithmeticException if the value can not be represented in the Java signed {@code long} type.
+     * @throws IllegalArgumentException if the value is not a singleton.
+     * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
     long readLong(ChannelDataInput input, long count) throws IOException {
-        // TODO: replace "<?>" by the actual tag name.
-        throw new InvalidTiffHeaderException(Errors.format(Errors.Keys.IllegalPropertyType_2, "<?>", name()));
+        throw new UnsupportedOperationException(name());
     }
 
     /**
@@ -423,8 +437,10 @@ enum Type {
      * @param  input  the input from where to read the value.
      * @param  count  the amount of values (normally exactly 1).
      * @return the value as a {@code double}.
-     * @throws InvalidTiffHeaderException if the value can not be converted to a {@code double}.
-     * @throws IOException if another error occurred while reading the stream.
+     * @throws IOException if an error occurred while reading the stream.
+     * @throws NumberFormatException if the value was stored in ASCII and can not be parsed.
+     * @throws IllegalArgumentException if the value is not a singleton.
+     * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
     double readDouble(ChannelDataInput input, long count) throws IOException {
         return readLong(input, count);
@@ -439,6 +455,7 @@ enum Type {
      * @return the value as a string.
      * @throws IOException if an error occurred while reading the stream.
      * @throws ArithmeticException if the given length is too large.
+     * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
      */
     String[] readString(ChannelDataInput input, final long length, final Charset charset) throws IOException {
         final String[] s = new String[Math.toIntExact(length)];

Modified: sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java?rev=1754039&r1=1754038&r2=1754039&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/package-info.java [UTF-8] Mon Jul 25 17:53:56 2016
@@ -18,6 +18,13 @@
 /**
  * Maps ISO metadata elements from/to the GeoTIFF tags.
  *
+ * <p>References:</p>
+ * <ul>
+ *   <li><a href="http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf">TIFF specification</a>: baseline</li>
+ *   <li><a href="http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf">TIFF Tecnical Notes</a>: add new compressions</li>
+ *   <li><a href="http://www.awaresystems.be/imaging/tiff/tifftags.html">TIFF Tag Reference</a></li>
+ * </ul>
+ *
  * @author  Rémi Maréchal (Geomatys)
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Minh Chinh Vu (VNSC)

Copied: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java (from r1754038, sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java?p2=sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java&p1=sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java&r1=1754038&r2=1754039&rev=1754039&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataHelper.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java [UTF-8] Mon Jul 25 17:53:56 2016
@@ -16,65 +16,347 @@
  */
 package org.apache.sis.internal.storage;
 
-import java.util.Collections;
+import java.util.Collection;
 import java.nio.charset.Charset;
+import org.opengis.metadata.constraint.Restriction;
+import org.opengis.referencing.ReferenceSystem;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.operation.TransformException;
-import org.apache.sis.util.Static;
-import org.apache.sis.setup.OptionKey;
-import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.geometry.AbstractEnvelope;
 import org.apache.sis.metadata.iso.DefaultMetadata;
+import org.apache.sis.metadata.iso.citation.AbstractParty;
 import org.apache.sis.metadata.iso.extent.DefaultExtent;
+import org.apache.sis.metadata.iso.citation.DefaultCitation;
+import org.apache.sis.metadata.iso.citation.DefaultIndividual;
+import org.apache.sis.metadata.iso.citation.DefaultOrganisation;
+import org.apache.sis.metadata.iso.citation.DefaultResponsibility;
+import org.apache.sis.metadata.iso.constraint.DefaultLegalConstraints;
 import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.Utilities;
+import org.apache.sis.util.iso.Types;
 
 
 /**
  * Helper methods for the metadata created by {@code DataStore} implementations.
+ * This class creates the metadata objects only when first needed.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @since   0.7
- * @version 0.7
+ * @since   0.8
+ * @version 0.8
  * @module
  */
-public final class MetadataHelper extends Static {
+public class MetadataBuilder {
     /**
-     * Do not allow instantiation of this class.
+     * Elements to ignore in the legal notice to be parsed by {@link #parseLegalNotice(String)}.
+     * Some of those elements are implied by the metadata were the legal notice will be stored.
      */
-    private MetadataHelper() {
+    private static final String[] IGNORABLE_NOTICE = {
+        "(C)", "®", "All rights reserved"
+    };
+
+    /**
+     * {@code true} for creating responsible parties as organization, or {@code false} for creating them as individual.
+     */
+    private boolean authorIsOrganization;
+
+    /**
+     * The metadata created by this reader, or {@code null} if none.
+     */
+    private DefaultMetadata metadata;
+
+    /**
+     * The identification information that are part of {@linkplain #metadata}, or {@code null} if none.
+     */
+    private DefaultDataIdentification identification;
+
+    /**
+     * The citation of data {@linkplain #identification}, or {@code null} if none.
+     */
+    private DefaultCitation citation;
+
+    /**
+     * Part of the responsible party of the {@linkplain #citation}, or {@code null} if none.
+     */
+    private DefaultResponsibility responsibility;
+
+    /**
+     * Part of the responsible party of the {@linkplain #citation}, or {@code null} if none.
+     */
+    private AbstractParty party;
+
+    /**
+     * The extent information that are part of {@linkplain #identification}, or {@code null} if none.
+     */
+    private DefaultExtent extent;
+
+    /**
+     * Copyright information, or {@code null} if none.
+     */
+    private DefaultLegalConstraints constraints;
+
+    /**
+     * Creates a new metadata reader.
+     */
+    public MetadataBuilder() {
     }
 
     /**
-     * Creates a metadata object for a text file parsed from the given connector.
+     * Returns the metadata as an unmodifiable object, or {@cod null} if none.
+     * After this method has been invoked, the metadata can not be modified anymore.
      *
-     * @param  connector The connector.
-     * @return A new, initially almost empty, metadata object.
+     * @return the metadata, or {@code null} if none.
      */
-    public static DefaultMetadata createForTextFile(final StorageConnector connector) {
-        final DefaultMetadata metadata = new DefaultMetadata();
-        final Charset encoding = connector.getOption(OptionKey.ENCODING);
-        if (encoding != null) {
-            metadata.setCharacterSets(Collections.singleton(encoding));
+    public final DefaultMetadata result() {
+        commit();
+        if (metadata != null) {
+            metadata.freeze();
         }
         return metadata;
     }
 
     /**
-     * Adds the given extent to the given metadata.
+     * Performs the links between the objects created in this builder. After this method has been invoked,
+     * all metadata objects should be reachable from the root {@link DefaultMetadata} object.
+     */
+    private void commit() {
+        /*
+         * Construction shall be ordered from children to parents.
+         */
+        if (extent != null) {
+            identification().getExtents().add(extent);
+            extent = null;
+        }
+        if (party != null) {
+            responsibility().getParties().add(party);
+            party = null;
+        }
+        if (responsibility != null) {
+            citation().getCitedResponsibleParties().add(responsibility);
+            responsibility = null;
+        }
+        if (citation != null) {
+            identification().setCitation(citation);
+            citation = null;
+        }
+        if (constraints != null) {
+            identification().getResourceConstraints().add(constraints);
+            constraints = null;
+        }
+        if (identification != null) {
+            metadata().getIdentificationInfo().add(identification);
+            identification = null;
+        }
+    }
+
+    /**
+     * Creates the metadata object if it does not already exists, then return it.
      *
-     * @param  addTo    The metadata where to add the extent.
-     * @param  envelope The extent to add in the given metadata, or {@code null} if none.
+     * @return the metadata (never {@code null}).
+     */
+    private DefaultMetadata metadata() {
+        if (metadata == null) {
+            metadata = new DefaultMetadata();
+        }
+        return metadata;
+    }
+
+    /**
+     * Creates the identification information object if it does not already exists, then return it.
+     *
+     * @return the identification information (never {@code null}).
+     */
+    private DefaultDataIdentification identification() {
+        if (identification == null) {
+            identification = new DefaultDataIdentification();
+        }
+        return identification;
+    }
+
+    /**
+     * Creates the citation object if it does not already exists, then return it.
+     *
+     * @return the citation information (never {@code null}).
+     */
+    private DefaultCitation citation() {
+        if (citation == null) {
+            citation = new DefaultCitation();
+        }
+        return citation;
+    }
+
+    /**
+     * Creates the responsibility object if it does not already exists, then return it.
+     *
+     * @return the responsibility party (never {@code null}).
+     */
+    private DefaultResponsibility responsibility() {
+        if (responsibility == null) {
+            responsibility = new DefaultResponsibility();
+        }
+        return responsibility;
+    }
+
+    /**
+     * Creates the person or organization information object if it does not already exists, then return it.
+     *
+     * @return the person or organization information (never {@code null}).
+     */
+    private AbstractParty party() {
+        if (party == null) {
+            party = authorIsOrganization ? new DefaultOrganisation() : new DefaultIndividual();
+        }
+        return party;
+    }
+
+    /**
+     * Creates the extent information object if it does not already exists, then return it.
+     *
+     * @return the extent information (never {@code null}).
+     */
+    private DefaultExtent extent() {
+        if (extent == null) {
+            extent = new DefaultExtent();
+        }
+        return extent;
+    }
+
+    /**
+     * Creates the constraints information object if it does not already exists, then return it.
+     *
+     * @return the constraints information (never {@code null}).
+     */
+    private DefaultLegalConstraints constraints() {
+        if (constraints == null) {
+            constraints = new DefaultLegalConstraints();
+        }
+        return constraints;
+    }
+
+    /**
+     * Adds the given character encoding to the metadata.
+     *
+     * @param encoding  the character encoding to add.
+     */
+    public final void add(final Charset encoding) {
+        if (encoding != null) {
+            metadata().getCharacterSets().add(encoding);
+        }
+    }
+
+    /**
+     * Adds the given coordinate reference system to metadata, if it does not already exists.
+     * This method ensures that there is no duplicated values. Comparisons ignore metadata.
+     *
+     * @param  crs  the coordinate reference system to add to the metadata, or {@code null} if none.
+     */
+    public final void add(final CoordinateReferenceSystem crs) {
+        if (crs != null) {
+            final Collection<ReferenceSystem> systems = metadata().getReferenceSystemInfo();
+            for (final ReferenceSystem existing : systems) {
+                if (Utilities.equalsIgnoreMetadata(crs, existing)) {
+                    return;
+                }
+            }
+            systems.add(crs);
+        }
+    }
+
+    /**
+     * Adds the given envelope, including its CRS, to the metadata. If the metadata already contains a geographic
+     * bounding box, then a new bounding box is added; this method does not compute the union of the two boxes.
+     *
+     * @param  envelope  the extent to add in the metadata, or {@code null} if none.
      * @throws TransformException if an error occurred while converting the given envelope to extents.
      */
-    public static void add(final DefaultMetadata addTo, final AbstractEnvelope envelope) throws TransformException {
+    public final void add(final AbstractEnvelope envelope) throws TransformException {
         if (envelope != null) {
-            addTo.setReferenceSystemInfo(Collections.singleton(envelope.getCoordinateReferenceSystem()));
+            add(envelope.getCoordinateReferenceSystem());
             if (!envelope.isAllNaN()) {
-                final DefaultExtent extent = new DefaultExtent();
-                extent.addElements(envelope);
-                final DefaultDataIdentification id = new DefaultDataIdentification();
-                id.setExtents(Collections.singleton(extent));
-                addTo.setIdentificationInfo(Collections.singleton(id));
+                extent().addElements(envelope);
+            }
+        }
+    }
+
+    /**
+     * Adds an author name.
+     *
+     * @param  author  the name of the author or publisher, or {@code null} if none.
+     */
+    public final void addAuthorName(final CharSequence author) {
+        if (author != null) {
+            if (party != null) {
+                responsibility().getParties().add(party);
+                party = null;
+            }
+            party().setName(Types.toInternationalString(author));
+        }
+    }
+
+    /**
+     * Parses the legal notice. The method expects a string of the form
+     * “Copyright, John Smith, 1992. All rights reserved.”
+     *
+     * @param  notice  the legal notice, or {@code null} if none.
+     */
+    public final void parseLegalNotice(final String notice) {
+        if (notice != null) {
+            int year = 0;                                           // The copyright year, or 0 if none.
+            Restriction restriction = null;                         // The kind of restriction (copyright, licence, etc.).
+            final int length = notice.length();
+            final StringBuilder name = new StringBuilder(length);   // Everything which is not one of the above or an ignored text.
+            int start = CharSequences.skipLeadingWhitespaces(notice, 0, length);
+parse:      for (int i = start; i < length;) {
+                final int c = notice.codePointAt(i);
+                final int n = Character.charCount(c);
+                if (!Character.isLetterOrDigit(c)) {
+                    /*
+                     * Ignore text like "(C)" or "All rights reserved". Some of those statements
+                     * are implied by the metadata were the legal notice will be stored.
+                     */
+                    for (final String ignorable : IGNORABLE_NOTICE) {
+                        if (notice.regionMatches(true, start, ignorable, 0, ignorable.length())) {
+                            start = i = CharSequences.skipLeadingWhitespaces(notice, i + ignorable.length(), length);
+                            continue parse;
+                        }
+                    }
+                    /*
+                     * Convert text like "Copyright" or "Licence" into one of the Restriction enumerated values.
+                     */
+                    if (restriction == null) {
+                        restriction = Types.forCodeName(Restriction.class, notice.substring(start, i), false);
+                        if (restriction != null) {
+                            constraints().getAccessConstraints().add(restriction);
+                            start = i = CharSequences.skipLeadingWhitespaces(notice, i, length);
+                            continue;
+                        }
+                    } else {
+                        /*
+                         * After we determined that the string begins by "Copyright" or another recognized string
+                         * and is not one of the ignorable statements, store the remaining either in the buffer
+                         * or as the copyright year.
+                         */
+                        if (year == 0 && Character.isDigit(notice.charAt(start))
+                                      && Character.isDigit(notice.codePointBefore(i)))
+                        {
+                            try {
+                                year = Integer.parseInt(notice.substring(start, i));
+                                if (year >= 1800 && year <= 9999) {   // Those limits are arbitrary.
+                                    start = i;                        // Accept as a copyright year.
+                                    i += n;
+                                    continue;
+                                } else {
+                                    year = 0;                         // Reject - not a copyright year.
+                                }
+                            } catch (NumberFormatException ex) {      // Ignore - not an integer.
+                            }
+                        }
+                        name.append(notice, start, i);
+                    }
+                }
+                i += n;
             }
+            // TODO: store year and name.
         }
     }
 }

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java?rev=1754039&r1=1754038&r2=1754039&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java [UTF-8] Mon Jul 25 17:53:56 2016
@@ -30,6 +30,7 @@ import java.io.BufferedReader;
 import java.io.LineNumberReader;
 import java.io.IOException;
 import java.net.URI;
+import java.nio.charset.Charset;
 import javax.measure.unit.Unit;
 import javax.measure.unit.SI;
 import javax.measure.unit.NonSI;
@@ -44,7 +45,7 @@ import org.apache.sis.feature.DefaultFea
 import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.internal.referencing.GeodeticObjectBuilder;
-import org.apache.sis.internal.storage.MetadataHelper;
+import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.geometry.GeneralEnvelope;
 import org.apache.sis.metadata.iso.DefaultMetadata;
 import org.apache.sis.storage.DataStore;
@@ -64,6 +65,7 @@ import org.opengis.feature.Feature;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
 import org.opengis.feature.AttributeType;
+import org.apache.sis.setup.OptionKey;
 
 
 /**
@@ -72,7 +74,7 @@ import org.opengis.feature.AttributeType
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
- * @version 0.7
+ * @version 0.8
  * @module
  */
 public final class Store extends DataStore {
@@ -119,9 +121,14 @@ public final class Store extends DataSto
     private BufferedReader source;
 
     /**
-     * The metadata object. Initialized to a minimal amount of information, then completed when first needed.
+     * The character encoding, or {@code null} if unspecified (in which case the platform default is assumed).
      */
-    private final DefaultMetadata metadata;
+    private final Charset encoding;
+
+    /**
+     * The metadata object, or {@code null} if not yet created.
+     */
+    private transient DefaultMetadata metadata;
 
     /**
      * The three- or four-dimensional envelope together with the CRS.
@@ -235,10 +242,10 @@ public final class Store extends DataSto
         } catch (IOException | FactoryException | IllegalArgumentException | DateTimeException e) {
             throw new DataStoreException(errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", name), e);
         }
+        this.encoding    = connector.getOption(OptionKey.ENCODING);
         this.envelope    = envelope;
         this.featureType = featureType;
         this.foliation   = foliation;
-        this.metadata    = MetadataHelper.createForTextFile(connector);
         this.features    = new ArrayList<>();
     }
 
@@ -456,13 +463,15 @@ public final class Store extends DataSto
      */
     @Override
     public Metadata getMetadata() throws DataStoreException {
-        if (metadata.isModifiable()) {
+        if (metadata == null) {
+            final MetadataBuilder builder = new MetadataBuilder();
+            builder.add(encoding);
             try {
-                MetadataHelper.add(metadata, envelope);
+                builder.add(envelope);
             } catch (TransformException e) {
                 throw new DataStoreException(errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", name), e);
             }
-            metadata.freeze();
+            metadata = builder.result();
         }
         return metadata;
     }
@@ -515,7 +524,7 @@ public final class Store extends DataSto
                          *   Column 1 is the start time.
                          *   Column 2 is the end time.
                          *   Column 3 is the trajectory.
-                         *   Columns 4+ are custum attributes.
+                         *   Columns 4+ are custom attributes.
                          *
                          * TODO: we should replace that switch case by custom ObjectConverter.
                          */



Mime
View raw message