sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1759105 [1/2] - in /sis/branches/JDK8: core/sis-utility/src/main/java/org/apache/sis/internal/util/ core/sis-utility/src/main/java/org/apache/sis/util/iso/ core/sis-utility/src/main/java/org/apache/sis/util/resources/ storage/sis-earth-obs...
Date Sat, 03 Sep 2016 16:39:34 GMT
Author: desruisseaux
Date: Sat Sep  3 16:39:34 2016
New Revision: 1759105

URL: http://svn.apache.org/viewvc?rev=1759105&view=rev
Log:
Refactor Landsat metadata reader for using MetadataBuilder.
This allow us to regroup the parsing of Landsat attribute in a single switch statement,
since many of the complexity previously in LandsatReader moved to MetadataBuilder.

Removed:
    sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatKeys.java
Modified:
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/SimpleInternationalString.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
    sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
    sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
    sis/branches/JDK8/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
    sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/MetadataBuilder.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
    sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java?rev=1759105&r1=1759104&r2=1759105&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CollectionsExt.java [UTF-8] Sat Sep  3 16:39:34 2016
@@ -51,7 +51,7 @@ import java.util.function.Function;
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.3
- * @version 0.6
+ * @version 0.8
  * @module
  */
 public final class CollectionsExt extends Static {
@@ -68,9 +68,9 @@ public final class CollectionsExt extend
      *
      * <p>This method is null-safe. Note however that the first element may be null.</p>
      *
-     * @param  <T> The type of elements contained in the iterable.
-     * @param  collection The iterable from which to get the first element, or {@code null}.
-     * @return The first element, or {@code null} if the given iterable is null or empty.
+     * @param  <T>         the type of elements contained in the iterable.
+     * @param  collection  the iterable from which to get the first element, or {@code null}.
+     * @return the first element, or {@code null} if the given iterable is null or empty.
      */
     public static <T> T first(final Iterable<T> collection) {
         if (collection != null) {
@@ -83,10 +83,41 @@ public final class CollectionsExt extend
     }
 
     /**
+     * If the given iterable contains exactly one non-null element, returns that element.
+     * Otherwise returns {@code null}.
+     *
+     * @param  <T>         the type of elements contained in the iterable.
+     * @param  collection  the iterable from which to get the singleton element, or {@code null}.
+     * @return the singleton element, or {@code null} if the given iterable is null or does not
+     *         contain exactly one non-null element.
+     *
+     * @since 0.8
+     */
+    public static <T> T singletonOrNull(final Iterable<T> collection) {
+        if (collection != null) {
+            final Iterator<T> it = collection.iterator();
+            if (it != null) {                                       // This check for null is paranoiac.
+                T element = null;
+                while (it.hasNext()) {
+                    final T next = it.next();
+                    if (next != null) {
+                        if (element != null) {
+                            return null;
+                        }
+                        element = next;
+                    }
+                }
+                return element;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns a {@linkplain Queue queue} which is always empty and accepts no element.
      *
-     * @param <E> The type of elements in the empty collection.
-     * @return An empty collection.
+     * @param  <E>  the type of elements in the empty collection.
+     * @return an empty collection.
      *
      * @see Collections#emptyList()
      * @see Collections#emptySet()
@@ -99,9 +130,9 @@ public final class CollectionsExt extend
     /**
      * Returns the given value as a singleton if non-null, or returns an empty set otherwise.
      *
-     * @param  <E> The element type.
-     * @param  element The element to returns in a collection if non-null.
-     * @return A collection containing the given element if non-null, or an empty collection otherwise.
+     * @param  <E>      the element type.
+     * @param  element  the element to returns in a collection if non-null.
+     * @return a collection containing the given element if non-null, or an empty collection otherwise.
      */
     public static <E> Set<E> singletonOrEmpty(final E element) {
         return (element != null) ? Collections.singleton(element) : Collections.emptySet();
@@ -111,9 +142,9 @@ public final class CollectionsExt extend
      * Returns a copy of the given array as a non-empty immutable set.
      * If the given array is empty, then this method returns {@code null}.
      *
-     * @param  <T> The type of elements.
-     * @param  elements The elements to copy in a set.
-     * @return An unmodifiable set which contains all the given elements, or {@code null}.
+     * @param  <T>       the type of elements.
+     * @param  elements  the elements to copy in a set.
+     * @return an unmodifiable set which contains all the given elements, or {@code null}.
      *
      * @since 0.6
      */
@@ -128,9 +159,9 @@ public final class CollectionsExt extend
      * This method is generally not recommended, since public API should prefer empty array instead of null.
      * However this method is occasionally useful for managing private fields.
      *
-     * @param  <E> The type of elements in the array.
-     * @param  array The array, or {@code null}.
-     * @return The given array, or {@code null} if the given array was empty.
+     * @param  <E>    the type of elements in the array.
+     * @param  array  the array, or {@code null}.
+     * @return the given array, or {@code null} if the given array was empty.
      */
     public static <E> E[] nonEmpty(final E[] array) {
         return (array != null && array.length == 0) ? null : array;
@@ -143,10 +174,10 @@ public final class CollectionsExt extend
      * with frameworks that may expect or return null (e.g. if we want to exclude completely an empty collection
      * from marshalling with JAXB).
      *
-     * @param  <T> The type of the collection.
-     * @param  <E> The type of elements in the collection.
-     * @param  c   The collection, or {@code null}.
-     * @return The given collection, or {@code null} if the given collection was empty.
+     * @param  <T>  the type of the collection.
+     * @param  <E>  the type of elements in the collection.
+     * @param  c    the collection, or {@code null}.
+     * @return the given collection, or {@code null} if the given collection was empty.
      */
     public static <T extends Collection<E>, E> T nonEmpty(final T c) {
         return (c != null && c.isEmpty()) ? null : c;
@@ -155,9 +186,9 @@ public final class CollectionsExt extend
     /**
      * Returns the given collection, or {@link Collections#EMPTY_SET} if the given collection is null.
      *
-     * @param  <E> The type of elements in the collection.
-     * @param  c The collection, or {@code null}.
-     * @return The given collection, or an empty set if the given collection was null.
+     * @param  <E>  the type of elements in the collection.
+     * @param  c    the collection, or {@code null}.
+     * @return the given collection, or an empty set if the given collection was null.
      */
     public static <E> Collection<E> nonNull(final Collection<E> c) {
         return (c != null) ? c : Collections.<E>emptySet();
@@ -166,9 +197,9 @@ public final class CollectionsExt extend
     /**
      * Returns the given set, or {@link Collections#EMPTY_SET} if the given set is null.
      *
-     * @param  <E> The type of elements in the collection.
-     * @param  c The collection, or {@code null}.
-     * @return The given collection, or an empty set if the given collection was null.
+     * @param  <E>  the type of elements in the collection.
+     * @param  c    the collection, or {@code null}.
+     * @return the given collection, or an empty set if the given collection was null.
      */
     public static <E> Set<E> nonNull(final Set<E> c) {
         return (c != null) ? c : Collections.<E>emptySet();
@@ -194,12 +225,12 @@ public final class CollectionsExt extend
      * constructors of {@code AbstractIdentifiedObject} subclasses receiving a map of properties,
      * and the contract of our constructors do not allow those other types for now.</div>
      *
-     * @param  <E>        The type of elements in the array to be returned.
-     * @param  name       The parameter name, used only for formatting an error message in case of failure.
-     * @param  value      The value to return as an array, or {@code null}.
-     * @param  emptyArray An instance of {@code new E[0]}. This argument can not be null.
-     * @return The given value as an array of {@code <E>}. Never null.
-     * throws  IllegalArgumentException If the given value is not null, an instance of {@code <E>}
+     * @param  <E>         the type of elements in the array to be returned.
+     * @param  name        the parameter name, used only for formatting an error message in case of failure.
+     * @param  value       the value to return as an array, or {@code null}.
+     * @param  emptyArray  an instance of {@code new E[0]}. This argument can not be null.
+     * @return the given value as an array of {@code <E>}. Never null.
+     * throws  IllegalArgumentException if the given value is not null, an instance of {@code <E>}
      *         or an array of {@code <E>}.
      *
      * @since 0.4
@@ -235,10 +266,10 @@ public final class CollectionsExt extend
      * Creates an initially empty set for elements of the given type.
      * This method will creates specialized set for code lists and enumerations.
      *
-     * @param  <E>   The type of elements in the set.
-     * @param  type  The type of elements in the set.
-     * @param  count The expected number of elements to put in the set.
-     * @return A new set for elements of the given type.
+     * @param  <E>    the type of elements in the set.
+     * @param  type   the type of elements in the set.
+     * @param  count  the expected number of elements to put in the set.
+     * @return a new set for elements of the given type.
      */
     @SuppressWarnings({"unchecked","rawtypes"})
     public static <E> Set<E> createSetForType(final Class<E> type, final int count) {
@@ -257,10 +288,10 @@ public final class CollectionsExt extend
      * sense of {@link Object#equals(Object)}, then only the last instance of the duplicated
      * values will be included in the returned set.
      *
-     * @param  <E>         The type of array elements.
-     * @param  excludeNull {@code true} for excluding the {@code null} element from the returned set.
-     * @param  array       The array to copy in a set. May be {@code null} or contain null elements.
-     * @return A set containing the array elements, or {@code null} if the given array was null.
+     * @param  <E>          the type of array elements.
+     * @param  excludeNull  {@code true} for excluding the {@code null} element from the returned set.
+     * @param  array        the array to copy in a set. May be {@code null} or contain null elements.
+     * @return a set containing the array elements, or {@code null} if the given array was null.
      *
      * @see Collections#unmodifiableSet(Set)
      */
@@ -303,9 +334,9 @@ public final class CollectionsExt extend
      * <strong>not</strong> be modified after this method call. In case of doubt, use the
      * standard {@link Collections#unmodifiableSet(Set)} method instead.</p>
      *
-     * @param  <E>  The type of elements in the set.
-     * @param  set  The set to make unmodifiable, or {@code null}.
-     * @return A unmodifiable version of the given set, or {@code null} if the given set was null.
+     * @param  <E>  the type of elements in the set.
+     * @param  set  the set to make unmodifiable, or {@code null}.
+     * @return a unmodifiable version of the given set, or {@code null} if the given set was null.
      */
     public static <E> Set<E> unmodifiableOrCopy(Set<E> set) {
         if (set != null) {
@@ -338,10 +369,10 @@ public final class CollectionsExt extend
      * <strong>not</strong> be modified after this method call. In case of doubt, use the
      * standard {@link Collections#unmodifiableMap(Map)} method instead.</p>
      *
-     * @param  <K>  The type of keys in the map.
-     * @param  <V>  The type of values in the map.
-     * @param  map  The map to make unmodifiable, or {@code null}.
-     * @return A unmodifiable version of the given map, or {@code null} if the given map was null.
+     * @param  <K>  the type of keys in the map.
+     * @param  <V>  the type of values in the map.
+     * @param  map  the map to make unmodifiable, or {@code null}.
+     * @return a unmodifiable version of the given map, or {@code null} if the given map was null.
      */
     public static <K,V> Map<K,V> unmodifiableOrCopy(Map<K,V> map) {
         if (map != null) {
@@ -382,9 +413,9 @@ public final class CollectionsExt extend
      *
      * This method may not preserve the {@link org.apache.sis.util.collection.CheckedContainer} interface.
      *
-     * @param  <E> The type of elements in the collection.
-     * @param  collection The collection to copy, or {@code null}.
-     * @return A copy of the given collection, or {@code null} if the given collection was null.
+     * @param  <E>         the type of elements in the collection.
+     * @param  collection  the collection to copy, or {@code null}.
+     * @return a copy of the given collection, or {@code null} if the given collection was null.
      */
     @SuppressWarnings("unchecked")
     public static <E> Collection<E> modifiableCopy(final Collection<E> collection) {
@@ -439,10 +470,10 @@ public final class CollectionsExt extend
      * <tr><td>{@link Map} other than above</td><td class="sep">{@link LinkedHashMap}</td></tr>
      * </table>
      *
-     * @param  <K> The type of keys in the map.
-     * @param  <V> The type of values in the map.
-     * @param  map The map to copy, or {@code null}.
-     * @return A copy of the given map, or {@code null} if the given map was null.
+     * @param  <K>  the type of keys in the map.
+     * @param  <V>  the type of values in the map.
+     * @param  map  the map to copy, or {@code null}.
+     * @return a copy of the given map, or {@code null} if the given map was null.
      */
     @SuppressWarnings("unchecked")
     public static <K,V> Map<K,V> modifiableCopy(final Map<K,V> map) {
@@ -472,10 +503,10 @@ public final class CollectionsExt extend
      * view. The intend is to avoid one level of indirection for performance and memory reasons.
      * This is okay only if the map is kept in a private field and never escape outside this class.
      *
-     * @param  <K> The type of keys in the map.
-     * @param  <V> The type of values in the map.
-     * @param  map The map to compact, or {@code null}.
-     * @return A potentially compacted map, or {@code null} if the given map was null.
+     * @param  <K>  the type of keys in the map.
+     * @param  <V>  the type of values in the map.
+     * @param  map  the map to compact, or {@code null}.
+     * @return a potentially compacted map, or {@code null} if the given map was null.
      */
     public static <K,V> Map<K,V> compact(final Map<K,V> map) {
         if (map != null) {
@@ -493,9 +524,9 @@ public final class CollectionsExt extend
      * in the given list after this method call. This method makes no guaranteed about whether
      * the returned list is modifiable or not.
      *
-     * @param  <E>  The type of elements in the list.
-     * @param  list The list for which to take a snapshot, or {@code null} if none.
-     * @return A snapshot of the given list, or {@code list} itself if null or unmodifiable.
+     * @param  <E>   the type of elements in the list.
+     * @param  list  the list for which to take a snapshot, or {@code null} if none.
+     * @return a snapshot of the given list, or {@code list} itself if null or unmodifiable.
      */
     @SuppressWarnings("unchecked")
     public static <E> List<E> snapshot(final List<E> list) {
@@ -530,8 +561,8 @@ public final class CollectionsExt extend
      *     List<?> list = toList(toCollection(object));
      * }
      *
-     * @param  value The value to return as a collection, or {@code null}.
-     * @return The value as a collection, or wrapped in a collection (never {@code null}).
+     * @param  value  the value to return as a collection, or {@code null}.
+     * @return the value as a collection, or wrapped in a collection (never {@code null}).
      */
     public static Collection<?> toCollection(final Object value) {
         if (value == null) {
@@ -580,9 +611,9 @@ public final class CollectionsExt extend
      *     List<?> list = toList(toCollection(object));
      * }
      *
-     * @param  <T> The type of elements in the given collection.
-     * @param  collection The collection to cast or copy to a list.
-     * @return The given collection as a list, or a copy of the given collection.
+     * @param  <T>         the type of elements in the given collection.
+     * @param  collection  the collection to cast or copy to a list.
+     * @return the given collection as a list, or a copy of the given collection.
      */
     public static <T> List<T> toList(final Collection<T> collection) {
         if (collection instanceof List<?>) {
@@ -596,10 +627,10 @@ public final class CollectionsExt extend
      * argument is not known at compile-time. If the {@code valueClass} is known at compile-time, then callers should
      * use {@link Collection#toArray(T[])} instead.
      *
-     * @param  <T>        The compile-time value of {@code valueClass}.
-     * @param  collection The collection from which to get the elements.
-     * @param  valueClass The runtime type of collection elements.
-     * @return The collection elements as an array, or {@code null} if {@code collection} is null.
+     * @param  <T>         the compile-time value of {@code valueClass}.
+     * @param  collection  the collection from which to get the elements.
+     * @param  valueClass  the runtime type of collection elements.
+     * @return the collection elements as an array, or {@code null} if {@code collection} is null.
      *
      * @since 0.6
      */
@@ -616,12 +647,12 @@ public final class CollectionsExt extend
      * Adds a value in a pseudo multi-values map. The multi-values map is simulated by a map of lists.
      * The map can be initially empty - lists will be created as needed.
      *
-     * @param  <K>   The type of key elements in the map.
-     * @param  <V>   The type of value elements in the lists.
-     * @param  map   The multi-values map where to add an element.
-     * @param  key   The key of the element to add. Can be null if the given map supports null keys.
-     * @param  value The value of the element to add. Can be null.
-     * @return The list where the given value has been added. May be unmodifiable.
+     * @param  <K>    the type of key elements in the map.
+     * @param  <V>    the type of value elements in the lists.
+     * @param  map    the multi-values map where to add an element.
+     * @param  key    the key of the element to add. Can be null if the given map supports null keys.
+     * @param  value  the value of the element to add. Can be null.
+     * @return the list where the given value has been added. May be unmodifiable.
      */
     public static <K,V> List<V> addToMultiValuesMap(final Map<K,List<V>> map, final K key, final V value) {
         final List<V> singleton = Collections.singletonList(value);
@@ -647,12 +678,12 @@ public final class CollectionsExt extend
      * <p>Code searching in the returned map shall ask for the original (non lower-case) name
      * <strong>before</strong> to ask for the lower-cases version of that name.</p>
      *
-     * @param  <E>          The type of elements.
-     * @param  elements     The elements to store in the map, or {@code null} if none.
-     * @param  nameFunction The function for computing a name from an element.
-     * @param  namesLocale  The locale to use for creating the "all lower cases" names.
-     * @return A (<cite>name</cite>, <cite>element</cite>) mapping with lower cases entries where possible.
-     * @throws InvalidParameterCardinalityException If the same name is used for more than one element.
+     * @param  <E>           the type of elements.
+     * @param  elements      the elements to store in the map, or {@code null} if none.
+     * @param  nameFunction  the function for computing a name from an element.
+     * @param  namesLocale   the locale to use for creating the "all lower cases" names.
+     * @return a (<cite>name</cite>, <cite>element</cite>) mapping with lower cases entries where possible.
+     * @throws InvalidParameterCardinalityException if the same name is used for more than one element.
      */
     public static <E> Map<String,E> toCaseInsensitiveNameMap(final Collection<? extends E> elements,
             final Function<E,String> nameFunction, final Locale namesLocale)
@@ -712,9 +743,9 @@ public final class CollectionsExt extend
      * Returns {@code true} if the next elements returned by the given iterators are the same.
      * This method compares using the identity operation ({@code ==}), not {@code equals(Object)}.
      *
-     * @param  it1 The first iterator.
-     * @param  it2 The second iterator.
-     * @return If both iterators return the same objects.
+     * @param  it1  the first iterator.
+     * @param  it2  the second iterator.
+     * @return if both iterators return the same objects.
      */
     public static boolean identityEquals(final Iterator<?> it1, final Iterator<?> it2) {
         while (it1.hasNext()) {

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/SimpleInternationalString.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/SimpleInternationalString.java?rev=1759105&r1=1759104&r2=1759105&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/SimpleInternationalString.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/iso/SimpleInternationalString.java [UTF-8] Sat Sep  3 16:39:34 2016
@@ -23,9 +23,6 @@ import java.io.Serializable;
 import java.util.Locale;
 import org.apache.sis.util.ArgumentChecks;
 
-// Branch-dependent imports
-import java.util.Objects;
-
 
 /**
  * An international string consisting of a single string for all locales.
@@ -58,7 +55,7 @@ public class SimpleInternationalString e
      * to wrap is the more generic {@code CharSequence} interface, then use
      * the {@link Types#toInternationalString(CharSequence)} method instead.
      *
-     * @param text The string for all locales.
+     * @param text the string for all locales.
      */
     public SimpleInternationalString(final String text) {
         ArgumentChecks.ensureNonNull("text", text);
@@ -76,8 +73,8 @@ public class SimpleInternationalString e
     /**
      * Returns the same string for all locales. This is the string given to the constructor.
      *
-     * @param  locale Ignored in the {@code SimpleInternationalString} implementation.
-     * @return The international string as a {@code String}.
+     * @param  locale  ignored in the {@code SimpleInternationalString} implementation.
+     * @return the international string as a {@code String}.
      */
     @Override
     public String toString(final Locale locale) {
@@ -87,14 +84,14 @@ public class SimpleInternationalString e
     /**
      * Compares this international string with the specified object for equality.
      *
-     * @param object The object to compare with this international string.
+     * @param  object  the object to compare with this international string.
      * @return {@code true} if the given object is equal to this string.
      */
     @Override
+    @SuppressWarnings("OverlyStrongTypeCast")
     public boolean equals(final Object object) {
         if (object != null && object.getClass() == getClass()) {
-            final SimpleInternationalString that = (SimpleInternationalString) object;
-            return Objects.equals(this.defaultValue, that.defaultValue);
+            return defaultValue.equals(((SimpleInternationalString) object).defaultValue);
         }
         return false;
     }
@@ -102,7 +99,7 @@ public class SimpleInternationalString e
     /**
      * Returns a hash code value for this international text.
      *
-     * @return The hash code value.
+     * @return the hash code value.
      */
     @Override
     public int hashCode() {
@@ -112,8 +109,8 @@ public class SimpleInternationalString e
     /**
      * Writes the string. This is required since {@link #defaultValue} is not serialized.
      *
-     * @param  out The output stream where to serialize this international string.
-     * @throws IOException If an I/O error occurred while writing.
+     * @param  out  the output stream where to serialize this international string.
+     * @throws IOException if an I/O error occurred while writing.
      */
     private void writeObject(final ObjectOutputStream out) throws IOException {
         out.defaultWriteObject();
@@ -123,9 +120,9 @@ public class SimpleInternationalString e
     /**
      * Reads the string. This is required since {@link #defaultValue} is not serialized.
      *
-     * @param  in The input stream from which to deserialize an international string.
-     * @throws IOException If an I/O error occurred while reading or if the stream contains invalid data.
-     * @throws ClassNotFoundException If the class serialized on the stream is not on the classpath.
+     * @param  in  the input stream from which to deserialize an international string.
+     * @throws IOException if an I/O error occurred while reading or if the stream contains invalid data.
+     * @throws ClassNotFoundException if the class serialized on the stream is not on the classpath.
      */
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1759105&r1=1759104&r2=1759105&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Sat Sep  3 16:39:34 2016
@@ -336,6 +336,11 @@ public final class Errors extends Indexe
         public static final short ExcessiveStringSize = 153;
 
         /**
+         * A “{0}” statement was expected at line {2} or “{1}”.
+         */
+        public static final short ExpectedStatementAtLine_3 = 237;
+
+        /**
          * No factory of kind ‘{0}’ found.
          */
         public static final short FactoryNotFound_1 = 205;
@@ -872,6 +877,11 @@ public final class Errors extends Indexe
         public static final short NotABackwardReference_1 = 199;
 
         /**
+         * “{0}” is not a key-value pair.
+         */
+        public static final short NotAKeyValuePair_1 = 236;
+
+        /**
          * Argument ‘{0}’ shall not be NaN (Not-a-Number).
          */
         public static final short NotANumber_1 = 89;

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1759105&r1=1759104&r2=1759105&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Sat Sep  3 16:39:34 2016
@@ -78,6 +78,7 @@ ExcessiveArgumentSize_3           = Argu
 ExcessiveListSize_2               = A size of {1} elements is excessive for the \u201c{0}\u201d list.
 ExcessiveNumberOfDimensions_1     = For this algorithm, {0} is an excessive number of dimensions.
 ExcessiveStringSize               = The character string is too long.
+ExpectedStatementAtLine_3         = A \u201c{0}\u201d statement was expected at line {2} or \u201c{1}\u201d.
 FactoryNotFound_1                 = No factory of kind \u2018{0}\u2019 found.
 FileNotFound_1                    = File \u201c{0}\u201d has not been found.
 ForbiddenAttribute_2              = Attribute \u201c{0}\u201d is not allowed for an object of type \u2018{1}\u2019.
@@ -181,6 +182,7 @@ NonTemporalUnit_1                 = \u20
 NonUniformScale                   = Scale is not uniform.
 NotABackwardReference_1           = No element for the \u201c{0}\u201d identifier, or the identifier is a forward reference.
 NotAnAffineTransform              = Transform is not affine.
+NotAKeyValuePair_1                = \u201c{0}\u201d is not a key-value pair.
 NotANumber_1                      = Argument \u2018{0}\u2019 shall not be NaN (Not-a-Number).
 NotAPrimitiveWrapper_1            = Class \u2018{0}\u2019 is not a primitive type wrapper.
 NotASingleton_1                   = The \u201c{0}\u201d collection is not a singleton.

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1759105&r1=1759104&r2=1759105&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Sat Sep  3 16:39:34 2016
@@ -75,6 +75,7 @@ ExcessiveArgumentSize_3           = L\u2
 ExcessiveListSize_2               = Une taille de {1} \u00e9l\u00e9ments est excessive pour la liste \u00ab\u202f{0}\u202f\u00bb.
 ExcessiveNumberOfDimensions_1     = Pour cet algorithme, {0} est un trop grand nombre de dimensions.
 ExcessiveStringSize               = La cha\u00eene de caract\u00e8res est trop longue.
+ExpectedStatementAtLine_3         = Une d\u00e9claration \u00ab\u202f{0}\u202f\u00bb \u00e9tait attendue \u00e0 la ligne {2} de \u00ab\u202f{1}\u202f\u00bb.
 FactoryNotFound_1                 = Aucune fabrique de type \u2018{0}\u2019 n\u2019a \u00e9t\u00e9 trouv\u00e9e.
 FileNotFound_1                    = Le fichier \u00ab\u202f{0}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9.
 ForbiddenAttribute_2              = L\u2019attribut \u00ab\u202f{0}\u202f\u00bb n\u2019est pas autoris\u00e9 pour un objet de type \u2018{1}\u2019.
@@ -178,6 +179,7 @@ NonTemporalUnit_1                 = \u00
 NonUniformScale                   = L\u2019\u00e9chelle n\u2019est pas uniforme.
 NotABackwardReference_1           = Il n\u2019y a pas d\u2019\u00e9l\u00e9ment pour l\u2019identifiant \u201c{0}\u201d, ou l\u2019identifiant est une r\u00e9f\u00e9rence vers l\u2019avant.
 NotAnAffineTransform              = La transformation n\u2019est pas affine.
+NotAKeyValuePair_1                = \u00ab\u202f{0}\u202f\u00bb n\u2019est pas une paire cl\u00e9-valeur.
 NotANumber_1                      = L\u2019argument \u2018{0}\u2019 ne doit pas \u00eatre NaN (Not-a-Number).
 NotAPrimitiveWrapper_1            = La classe \u2018{0}\u2019 n\u2019est pas un adaptateur d\u2019un type primitif.
 NotASingleton_1                   = La collection de \u00ab\u202f{0}\u202f\u00bb n\u2019est pas un singleton.

Modified: sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java?rev=1759105&r1=1759104&r2=1759105&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatReader.java [UTF-8] Sat Sep  3 16:39:34 2016
@@ -20,57 +20,45 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.Locale;
-import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.io.LineNumberReader;
 import javax.measure.unit.Unit;
 import javax.measure.quantity.Length;
 import javax.measure.unit.SI;
 
 import org.opengis.metadata.Metadata;
-import org.opengis.metadata.acquisition.AcquisitionInformation;
-import org.opengis.metadata.acquisition.OperationType;
+import org.opengis.metadata.citation.Citation;
 import org.opengis.metadata.citation.DateType;
+import org.opengis.metadata.content.ContentInformation;
 import org.opengis.metadata.content.CoverageContentType;
-import org.opengis.metadata.content.ImageDescription;
-import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.identification.Identification;
-import org.opengis.metadata.identification.Progress;
 import org.opengis.metadata.maintenance.ScopeCode;
 
-import org.apache.sis.metadata.iso.DefaultIdentifier;
 import org.apache.sis.metadata.iso.DefaultMetadata;
-import org.apache.sis.metadata.iso.DefaultMetadataScope;
-import org.apache.sis.metadata.iso.acquisition.DefaultAcquisitionInformation;
-import org.apache.sis.metadata.iso.acquisition.DefaultEvent;
-import org.apache.sis.metadata.iso.acquisition.DefaultInstrument;
-import org.apache.sis.metadata.iso.acquisition.DefaultOperation;
-import org.apache.sis.metadata.iso.acquisition.DefaultPlatform;
 import org.apache.sis.metadata.iso.citation.Citations;
-import org.apache.sis.metadata.iso.citation.DefaultCitation;
-import org.apache.sis.metadata.iso.citation.DefaultCitationDate;
 import org.apache.sis.metadata.iso.content.DefaultAttributeGroup;
 import org.apache.sis.metadata.iso.content.DefaultBand;
-import org.apache.sis.metadata.iso.content.DefaultImageDescription;
-import org.apache.sis.metadata.iso.distribution.DefaultFormat;
-import org.apache.sis.metadata.iso.extent.DefaultExtent;
-import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
-import org.apache.sis.metadata.iso.extent.DefaultTemporalExtent;
-import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
+import org.apache.sis.metadata.iso.content.DefaultCoverageDescription;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.util.iso.SimpleInternationalString;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.logging.WarningListeners;
+import org.apache.sis.util.iso.SimpleInternationalString;
+import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.internal.util.StandardDateFormat;
 
 import static java.util.Collections.singleton;
-import static org.apache.sis.storage.earthobservation.LandsatKeys.*;
+import static org.apache.sis.internal.util.CollectionsExt.singletonOrNull;
 
 // Branch-dependent imports
 import java.time.LocalDate;
 import java.time.OffsetDateTime;
 import java.time.OffsetTime;
-import java.time.format.DateTimeParseException;
-import java.time.temporal.ChronoField;
-import org.opengis.metadata.acquisition.Context;
+import java.time.DateTimeException;
+import java.time.temporal.Temporal;
 import org.opengis.metadata.content.AttributeGroup;
 
 
@@ -80,11 +68,24 @@ import org.opengis.metadata.content.Attr
  * until the first occurrence of the {@code END} keyword. Lines beginning with the
  * {@code #} character (ignoring spaces) are treated as comment lines and ignored.
  *
+ * <p>This class will parse properties found in the Landsat metadata file,
+ * except {@code GROUP} and {@code END_GROUP}. Example:
+ *
+ * {@preformat text
+ *   DATE_ACQUIRED         = 2014-03-12
+ *   SCENE_CENTER_TIME     = 03:02:01.5339408Z
+ *   CORNER_UL_LAT_PRODUCT = 12.61111
+ *   CORNER_UL_LON_PRODUCT = 108.33624
+ *   CORNER_UR_LAT_PRODUCT = 12.62381
+ *   CORNER_UR_LON_PRODUCT = 110.44017
+ * }
+ *
  * <p><b>NOTE FOR MAINTAINER:</b> if the work performed by this class is modified, consider updating
  * <a href="./doc-files/LandsatMetadata.html">./doc-files/LandsatMetadata.html</a> accordingly.</p>
  *
  * @author  Thi Phuong Hao Nguyen (VNSC)
  * @author  Rémi Maréchal (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8
  * @version 0.8
  * @module
@@ -128,18 +129,33 @@ final class LandsatReader {
     }
 
     /**
-     * All properties found in the Landsat metadata file, except {@code GROUP} and {@code END_GROUP}. Example:
+     * The pattern determining if the value of {@code ORIGIN} key is of the form
+     * “Image courtesy of the U.S. Geological Survey”.
+     */
+    static final Pattern CREDIT = Pattern.compile("\\bcourtesy\\h+of\\h+(the)?\\b\\s*", Pattern.CASE_INSENSITIVE);
+
+    /**
+     * The keyword for end of metadata file.
+     */
+    private static final String END = "END";
+
+    /**
+     * An identifier of the file being read, or {@code null} if unknown.
+     * This is used mostly for formatting error messages.
      *
-     * {@preformat text
-     *   DATE_ACQUIRED         = 2014-03-12
-     *   SCENE_CENTER_TIME     = 03:02:01.5339408Z
-     *   CORNER_UL_LAT_PRODUCT = 12.61111
-     *   CORNER_UL_LON_PRODUCT = 108.33624
-     *   CORNER_UR_LAT_PRODUCT = 12.62381
-     *   CORNER_UR_LON_PRODUCT = 110.44017
-     * }
+     * @see #getFilename()
+     */
+    private String filename;
+
+    /**
+     * Helper class for building the ISO 19115 metadata instance.
+     */
+    private final MetadataBuilder metadata;
+
+    /**
+     * The locale to use for formatting warning or error messages.
      */
-    private final Map<String, String> properties;
+    private final Locale locale;
 
     /**
      * Where to send the warnings.
@@ -147,7 +163,45 @@ final class LandsatReader {
     private final WarningListeners<?> listeners;
 
     /**
-     * Creates a new metadata parser from the given characters reader.
+     * Group in process of being parsed, or {@code null} if none.
+     */
+    private String group;
+
+    /**
+     * The acquisition time, or {@code null} if not yet known. This needs to be parsed in two steps:
+     * first by parsing the {@code "DATE_ACQUIRED"} attribute, then {@code "SCENE_CENTER_TIME"}.
+     *
+     * @see #flushSceneTime()
+     */
+    private Temporal sceneTime;
+
+    /**
+     * Projected and geographic coordinate values, stocked temporarily before to be saved in the extent.
+     * Values are in (<var>x</var>,<var>y</var>) or (<var>lon</var>,<var>lat</var>) order.
+     * The first 8 values are the projected ones. The next 8 values are the geographic ones.
+     * Corner order is UL, UR, LL, LR.
+     */
+    private final double[] corners;
+
+    /**
+     * Creates a new metadata parser.
+     *
+     * @param  filename   an identifier of the file being read, or {@code null} if unknown.
+     * @param  locale     the locale to use for formatting warning or error messages.
+     * @param  listeners  where to sent warnings that may occur during the parsing process.
+     */
+    LandsatReader(final String filename, final Locale locale, final WarningListeners<?> listeners) {
+        this.filename  = filename;
+        this.locale    = locale;
+        this.listeners = listeners;
+        this.metadata  = new MetadataBuilder();
+        this.corners   = new double[16];
+        Arrays.fill(corners, Double.NaN);
+    }
+
+    /**
+     * Parses the metadata from the given characters reader.
+     * The parsing stop after the first {@code "END"} keyword.
      * See class javadoc for more information on the expected format.
      *
      * @param  reader  a reader opened on the Landsat file.
@@ -155,334 +209,404 @@ final class LandsatReader {
      * @throws IOException if an I/O error occurred while reading the given stream.
      * @throws DataStoreException if the content is not a Landsat file.
      */
-    LandsatReader(final BufferedReader reader, final WarningListeners<?> listeners)
-            throws IOException, DataStoreException
-    {
-        this.listeners = listeners;
-        properties = new HashMap<>();
+    void read(final BufferedReader reader) throws IOException, DataStoreException {
+        metadata.newCoverage(true);   // Starts the description of a new image.
         String line;
         while ((line = reader.readLine()) != null) {
-            line = line.trim();
-            if (!line.isEmpty() && line.charAt(0) != '#') {
-                /*
-                 * Landsat metadata ends with the END keyword. If we find that keyword, stop reading.
-                 * All remaining lines (if any) will be ignored.
-                 */
-                if (line.equals("END")) {
-                    break;
-                }
+            int end  = CharSequences.skipTrailingWhitespaces(line, 0, line.length());
+            int start = CharSequences.skipLeadingWhitespaces(line, 0, end);
+            if (start < end && line.charAt(start) != '#') {
                 /*
                  * Separate the line into its key and value. For example in CORNER_UL_LAT_PRODUCT = 12.61111,
                  * the key will be CORNER_UL_LAT_PRODUCT and the value will be 12.61111.
                  */
-                int separator = line.indexOf('=');
+                final int separator = line.indexOf('=', start);
                 if (separator < 0) {
-                    throw new DataStoreException("Not a key-value pair.");
-                }
-                String key = line.substring(0, separator).trim().toUpperCase(Locale.US);
-                if (!key.equals("GROUP") && !key.equals("END_GROUP")) {
-                    String value = line.substring(separator + 1).trim();
-                    if (key.isEmpty()) {
-                        throw new DataStoreException("Key shall not be empty.");
+                    /*
+                     * Landsat metadata ends with the END keyword, without value after that keyword.
+                     * If we find it, stop reading. All remaining lines (if any) will be ignored.
+                     * If a group was opened but not closed, report a warning.
+                     */
+                    if (end - start != END.length() || !line.regionMatches(true, start, END, 0, END.length())) {
+                        throw new DataStoreException(errors().getString(Errors.Keys.NotAKeyValuePair_1, line));
                     }
+                    if (group != null) {
+                        missingEndGroup(reader);
+                    }
+                    return;
+                } else {
+                    final String key = line.substring(start,
+                            CharSequences.skipTrailingWhitespaces(line, start, separator)).toUpperCase(Locale.US);
+                    start = CharSequences.skipLeadingWhitespaces(line, separator + 1, end);
                     /*
                      * In a Landsat file, String values are between quotes. Example: STATION_ID = "LGN".
                      * If such quotes are found, remove them.
                      */
-                    int length = value.length();
-                    if (length >= 2 && value.charAt(0) == '"' && value.charAt(length - 1) == '"') {
-                        value = value.substring(1, length - 1).trim();
-                        length = value.length();
+                    if (end - start >= 2 && line.charAt(start) == '"' && line.charAt(end - 1) == '"') {
+                        start = CharSequences.skipLeadingWhitespaces(line, start + 1, --end);
+                        end = CharSequences.skipTrailingWhitespaces(line, start, end);
                     }
-                    /*
-                     * Store only non-empty values. If a different value was already specified for the same key,
-                     * this is considered as an error.
-                     */
-                    if (length != 0) {
-                        String previous = properties.put(key, value);
-                        if (previous != null && !value.equals(previous)) {
-                            throw new DataStoreException("Duplicated values for \"" + key + "\".");
-                        }
+                    try {
+                        parseKeyValuePair(key, line.substring(start, end), reader);
+                    } catch (IllegalArgumentException | DateTimeException e) {
+                        warning(e);
                     }
                 }
             }
         }
+        warning(Errors.Keys.UnexpectedEndOfFile_1, getFilename());
     }
 
     /**
-     * Returns the property value associated to the given key, or {@code null} if none.
-     *
-     * @param  key  the key for which to get the property value.
-     * @return the property value associated to the given key, {@code null} if none.
+     * Parses the given value and stores its value at the given index in the {@link #corners} array.
      */
-    private String getValue(String key) {
-        return properties.get(key);
+    private void parseCorner(final int index, final String value) throws NumberFormatException {
+        corners[index] = Double.parseDouble(value);
     }
 
     /**
-     * Returns the floating-point value associated to the given key, or {@code NaN} if none.
+     * Invoked for every key-value pairs found in the file.
+     * Leading and trailing spaces, if any, have been removed.
      *
-     * @param  key the key for which to get the floating-point value.
-     * @return the floating-point value associated to the given key, or {@link Double#NaN} if none.
-     * @throws NumberFormatException if the property associated to the given key can not be parsed
-     *         as a floating-point number.
+     * @param  key     the key in upper cases.
+     * @param  value   the value, without quotes if those quotes existed.
+     * @param  reader  used only for reporting line number of warnings, if any.
+     * @throws NumberFormatException if the value was expected to be a string but the parsing failed.
+     * @throws DateTimeException if the value was expected to be a date but the parsing failed,
+     *         or if the result of the parsing was not of the expected type.
+     * @throws IllegalArgumentException if the value is out of range.
      */
-    private double getNumericValue(String key) throws NumberFormatException {
-        String value = getValue(key);
-        return (value != null) ? Double.parseDouble(value) : Double.NaN;
-    }
+    private void parseKeyValuePair(final String key, final String value, final BufferedReader reader)
+            throws IllegalArgumentException, DateTimeException
+    {
+        switch (key) {
+            case "GROUP": {
+                group = value;
+                break;
+            }
+            case "END_GROUP": {
+                if (!value.equals(group) && group != null) {
+                    missingEndGroup(reader);
+                }
+                group = null;
+                break;
+            }
 
-    /**
-     * Returns the minimal or maximal value associated to the given two keys, or {@code NaN} if none.
-     *
-     * @param  key1  the key for which to get the first floating-point value.
-     * @param  key2  the key for which to get the second floating-point value.
-     * @param  max   {@code true} for the maximal value, or {@code false} for the minimal value.
-     * @return the minimal (if {@code max} is false) or maximal (if {@code max} is true) floating-point value
-     *         associated to the given keys, or {@link Double#NaN} if none.
-     * @throws NumberFormatException if the properties associated to the given keys can not be parsed
-     *         as floating-point numbers.
-     */
-    private double getExtremumValue(String key1, String key2, boolean max) throws NumberFormatException {
-        double value1 = getNumericValue(key1);
-        double value2 = getNumericValue(key2);
-        if (max ? (value2 > value1) : (value2 < value1)) {
-            return value2;
-        } else {
-            return value1;
+            ////
+            //// GROUP = METADATA_FILE_INFO
+            ////
+
+            /*
+             * Origin of the product.
+             * Value is "Image courtesy of the U.S. Geological Survey".
+             */
+            case "ORIGIN": {
+                final Matcher m = CREDIT.matcher(value);
+                if (m.find()) {
+                    metadata.newParty(MetadataBuilder.ORGANISATION);
+                    metadata.addAuthor(value.substring(m.end()));
+                }
+                metadata.addCredits(value);
+                break;
+            }
+            /*
+             * Product Request ID. NNNNNNNNNNNNN_UUUUU, where NNNNNNNNNNNNN = 13-digit Tracking,
+             * Routing, and Metrics (TRAM) order number and UUUUU = 5-digit TRAM unit number.
+             * Example: "0501403126384_00011"
+             */
+// TODO     case "REQUEST_ID":
+            /*
+             * The unique Landsat scene identifier.
+             * Format is {@code Ls8ppprrrYYYYDDDGGGVV}.
+             * Example: "LC81230522014071LGN00".
+             */
+            case "LANDSAT_SCENE_ID": {
+                metadata.addIdentifier(value);
+                break;
+            }
+            /*
+             * The date when the metadata file for the L1G product set was created.
+             * The date is based on Universal Time Coordinated (UTC).
+             * Date format is {@code YYYY-MM-DDTHH:MM:SSZ}.
+             * Example: "2014-03-12T06:06:35Z".
+             */
+            case "FILE_DATE": {
+                metadata.add(StandardDateFormat.toDate(OffsetDateTime.parse(value)), DateType.CREATION);
+                break;
+            }
+            /*
+             * The Ground Station that received the data. Grounds station identifiers are specified in LSDS-547.
+             * Example: "LGN" = Landsat Ground Network.
+             */
+// TODO     case "STATION_ID":
+            /*
+             * The processing software version that created the product. Can be "IAS_X.Y.Z" or "LPGS_X.Y.Z"
+             * where X, Y and Z are major, minor and patch version numbers.
+             * Example: "LPGS_2.3.0".
+             */
+// TODO     case "PROCESSING_SOFTWARE_VERSION":
+
+            ////
+            //// GROUP = PRODUCT_METADATA
+            ////
+
+            /*
+             * The identifier to inform the user of the product type.
+             * Value can be "L1T" or "L1GT".
+             */
+// TODO     case "DATA_TYPE":
+            /*
+             * Indicates the source of the DEM used in the correction process.
+             * Value can be "GLS2000", "RAMP" or "GTOPO30".
+             */
+// TODO     case "ELEVATION_SOURCE":
+            /*
+             * The output format of the image.
+             * Value is "GEOTIFF".
+             */
+            case "OUTPUT_FORMAT": {
+                metadata.addFormat(value);
+                break;
+            }
+            /*
+             * Spacecraft from which the data were captured.
+             * Example: "LANDSAT_8".
+             */
+            case "SPACECRAFT_ID": {
+                metadata.addPlatform(value);
+                break;
+            }
+            /*
+             * Sensor(s) used to capture this scene.
+             * Example: "OLI", "TIRS" or "OLI_TIRS".
+             */
+            case "SENSOR_ID": {
+                metadata.addInstrument(value);
+                break;
+            }
+            /*
+             * The date the image was acquired.
+             * Date format is {@code YYYY-MM-DD}.
+             * Example: "2014-03-12".
+             */
+            case "DATE_ACQUIRED": {
+                final LocalDate date = LocalDate.parse(value);
+                if (sceneTime instanceof OffsetTime) {
+                    sceneTime = date.atTime((OffsetTime) sceneTime);
+                } else if (!date.equals(sceneTime)) {
+                    flushSceneTime();
+                    sceneTime = date;
+                }
+                break;
+            }
+            /*
+             * Scene center time of the date the image was acquired.
+             * Time format is {@code HH:MI:SS.SSSSSSSZ}.
+             * Example: "03:02:01.5339408Z".
+             */
+            case "SCENE_CENTER_TIME": {
+                final OffsetTime time = OffsetTime.parse(value);
+                if (sceneTime instanceof LocalDate) {
+                    sceneTime = ((LocalDate) sceneTime).atTime(time);
+                } else {
+                    sceneTime = time;
+                }
+                break;
+            }
+            /*
+             * The longitude and latitude values for the upper-left (UL), upper-right (UR), lower-left (LL)
+             * and lower-right (LR) corners of the product, measured at the center of the pixel.
+             * Positive longitude value indicates east longitude; negative value indicates west longitude.
+             * Positive latitude value indicates north latitude; negative value indicates south latitude.
+             * Units are in degrees.
+             */
+            case "CORNER_UL_LON_PRODUCT": parseCorner( 8, value); break;
+            case "CORNER_UL_LAT_PRODUCT": parseCorner( 9, value); break;
+            case "CORNER_UR_LON_PRODUCT": parseCorner(10, value); break;
+            case "CORNER_UR_LAT_PRODUCT": parseCorner(11, value); break;
+            case "CORNER_LL_LON_PRODUCT": parseCorner(12, value); break;
+            case "CORNER_LL_LAT_PRODUCT": parseCorner(13, value); break;
+            case "CORNER_LR_LON_PRODUCT": parseCorner(14, value); break;
+            case "CORNER_LR_LAT_PRODUCT": parseCorner(15, value); break;
+            /*
+             * The file name for L1 metadata.
+             * Exemple: "LC81230522014071LGN00_MTL.txt".
+             */
+            case "METADATA_FILE_NAME": {
+                if (filename == null) {
+                    filename = value;
+                }
+                break;
+            }
+
+            ////
+            //// GROUP = IMAGE_ATTRIBUTES
+            ////
+
+            /*
+             * The overall cloud coverage (percent) of the WRS-2 scene as a value between 0 and 100 inclusive.
+             * -1 indicates that the score was not calculated.
+             */
+            case "CLOUD_COVER": {
+                final double v = Double.parseDouble(value);
+                if (v >= 0) metadata.setCloudCoverPercentage(v);
+                break;
+            }
+            /*
+             * The Sun azimuth angle in degrees for the image center location at the image center acquisition time.
+             * Values are from -180 to 180 degrees inclusive.
+             * A positive value indicates angles to the east or clockwise from the north.
+             * A negative value indicates angles to the west or counterclockwise from the north.
+             */
+            case "SUN_AZIMUTH": {
+                metadata.setIlluminationAzimuthAngle(Double.parseDouble(value));
+                break;
+            }
+            /*
+             * The Sun elevation angle in degrees for the image center location at the image center acquisition time.
+             * Values are from -90 to 90 degrees inclusive.
+             * A positive value indicates a daytime scene. A negative value indicates a nighttime scene.
+             * Note: for reflectance calculation, the sun zenith angle is needed, which is 90 - sun elevation angle.
+             */
+            case "SUN_ELEVATION": {
+                metadata.setIlluminationElevationAngle(Double.parseDouble(value));
+                break;
+            }
         }
     }
 
     /**
-     * Returns the date associated to the given key, or {@code null} if none.
-     * The date is expected to be formatted in ISO 8601 format.
+     * Writes the value of {@link #sceneTime} into the metadata object as a temporal extent.
      *
-     * @param  key the key for which to get the date value.
-     * @return the date associated to the given key, or {@code null} if none.
-     * @throws DateTimeParseException if the date can not be parsed.
-     */
-    private Date getDate(final String key) throws DateTimeParseException {
-        final String value = getValue(key);
-        if (value == null) {
-            return null;
+     * @throws DateTimeException if {@link #sceneTime} is an instance of {@link OffsetTime}. This may
+     *         happen if {@code SCENE_CENTER_TIME} attribute was found without {@code DATE_ACQUIRED}.
+     */
+    private void flushSceneTime() {
+        final Temporal st = sceneTime;
+        if (st != null) {
+            sceneTime = null;                   // Clear now in case an exception it thrown below.
+            final Date t = StandardDateFormat.toDate(st);
+            metadata.addAcquisitionTime(t);
+            try {
+                metadata.addExtent(t, t);
+            } catch (UnsupportedOperationException e) {
+                // May happen if the temporal module (which is optional) is not on the classpath.
+                warning(e);
+            }
         }
-        final OffsetDateTime time = OffsetDateTime.parse(value);
-        return new Date(time.toEpochSecond() * 1000 + time.getNano() / 1000000);
     }
 
     /**
-     * Returns the date and time associated to the given key, or {@code null} if none.
-     * The date and time are expected to be in two separated fields, with each field
-     * formatted in ISO 8601 format.
-     *
-     * @param  dateKey  the key for which to get the date value.
-     * @param  timeKey  the key for which to get the time value.
-     * @return the date and time associated to the given keys, or {@code null} if none.
-     * @throws DateTimeParseException if the date can not be parsed.
-     */
-    private Date getDate(final String dateKey, final String timeKey) throws DateTimeParseException {
-        String value = getValue(dateKey);
-        if (value == null) {
-            return null;
-        }
-        final LocalDate date = LocalDate.parse(value);
-        value = getValue(timeKey);
-        final long millis;
-        if (value == null) {
-            millis = date.getLong(ChronoField.INSTANT_SECONDS) * 1000;
-        } else {
-            final OffsetDateTime time = date.atTime(OffsetTime.parse(value));
-            millis = time.toEpochSecond() * 1000 + time.getNano() / 1000000;
+     * Computes the bounding box for the 8 {@link #corners} values starting at the given index.
+     * Valid indices are 0 for the projected envelope or 8 for the geographic bounding box.
+     * Result is stored in the 4 values starting the given {@code base} index.
+     *
+     * @return {@code true} of success, or {@code false} if there is no bounding box.
+     */
+    private boolean toBoundingBox(int base) {
+        double xmin = Double.POSITIVE_INFINITY;
+        double ymin = Double.POSITIVE_INFINITY;
+        double xmax = Double.NEGATIVE_INFINITY;
+        double ymax = Double.NEGATIVE_INFINITY;
+        for (int i = base+8; --i >= 0;) {
+            double v = corners[i];
+            if (v < ymin) ymin = v;
+            if (v > ymax) ymax = v;
+            v = corners[--i];
+            if (v < xmin) xmin = v;
+            if (v > xmax) xmax = v;
+        }
+        if (xmin < xmax && ymin < ymax) {
+            corners[  base] = xmin;
+            corners[++base] = xmax;
+            corners[++base] = ymin;
+            corners[++base] = ymax;
+            return true;
         }
-        return new Date(millis);
+        return false;
     }
 
     /**
-     * Gets information about an image's suitability for use.
-     *
-     * @throws DataStoreException if a property value can not be parsed as a number or a date.
+     * Returns the metadata about the resources described in the Landsat file.
+     * The {@link #read(BufferedReader)} method must be invoked at least once before.
      */
-    private ImageDescription createImageDescription() throws DataStoreException {
-        final DefaultImageDescription content = new DefaultImageDescription();
+    final Metadata getMetadata() {
+        metadata.add(Locale.ENGLISH);
+        metadata.add(ScopeCode.DATASET);
         try {
-            double value;
-            if (0 <= (value = getNumericValue(CLOUD_COVER))) {
-                content.setCloudCoverPercentage(value);
-            }
-            if (!Double.isNaN(value = getNumericValue(SUN_AZIMUTH))) {
-                content.setIlluminationAzimuthAngle(value);
+            flushSceneTime();
+        } catch (DateTimeException e) {
+            // May happen if the SCENE_CENTER_TIME attribute was found without DATE_ACQUIRED.
+            warning(e);
+        }
+        if (toBoundingBox(8)) {
+            metadata.addExtent(corners, 8);
+        }
+        final DefaultMetadata result = metadata.build(false);
+        if (result != null) {
+            /*
+             * If there is exactly one data identification (which is usually the case, unless the user has invoked the
+             * read(BufferedReader) method many times), use the same identifier and date for the metadata as a whole.
+             */
+            final Identification id = singletonOrNull(result.getIdentificationInfo());
+            if (id != null) {
+                final Citation citation = id.getCitation();
+                if (citation != null) {
+                    result.setMetadataIdentifier(singletonOrNull(citation.getIdentifiers()));
+                    result.setDateInfo(singleton(singletonOrNull(citation.getDates())));
+                }
             }
-            if (!Double.isNaN(value = getNumericValue(SUN_ELEVATION))) {
-                content.setIlluminationElevationAngle(value);
+            /*
+             * Set pre-defined information about all bands.
+             */
+            final ContentInformation content = singletonOrNull(result.getContentInfo());
+            if (content instanceof DefaultCoverageDescription) {
+                ((DefaultCoverageDescription) content).setAttributeGroups(singleton(BANDS));
             }
-        } catch (NumberFormatException e) {
-            throw new DataStoreException("Can not read content information.", e);
+            result.setMetadataStandards(Citations.ISO_19115);
+            result.freeze();
         }
-        content.setAttributeGroups(singleton(BANDS));
-        return content;
+        return result;
     }
 
     /**
-     * Gets the geographic and temporal extent for identification info, or {@code null} if none.
-     * This method expects the data acquisition time in argument in order to avoid to compute it twice.
-     *
-     * @param  sceneTime the data acquisition time, or {@code null} if none.
-     * @return the data extent in Identification info, or {@code null} if none.
-     * @throws DataStoreException if a property value can not be parsed as a number or a date.
+     * Returns the filename to show in error messages, or a localized "unnamed" word if none.
      */
-    private Extent createExtent(final Date sceneTime) throws DataStoreException {
-        final DefaultGeographicBoundingBox box;
-        try {
-            box = new DefaultGeographicBoundingBox(
-                    getExtremumValue(CORNER_UL_LON_PRODUCT, CORNER_LL_LON_PRODUCT, false),      // westBoundLongitude
-                    getExtremumValue(CORNER_UR_LON_PRODUCT, CORNER_LR_LON_PRODUCT, true),       // eastBoundLongitude
-                    getExtremumValue(CORNER_LL_LAT_PRODUCT, CORNER_LR_LAT_PRODUCT, false),      // southBoundLatitude
-                    getExtremumValue(CORNER_UL_LAT_PRODUCT, CORNER_UR_LAT_PRODUCT, true));      // northBoundLatitude
-        } catch (NumberFormatException e) {
-            throw new DataStoreException("Can not read the geographic bounding box.", e);
-        }
-        final DefaultExtent extent = new DefaultExtent();
-        boolean isEmpty = box.isEmpty();
-        if (!isEmpty) {
-            extent.setGeographicElements(singleton(box));
-        }
-        if (sceneTime != null) {
-            try {
-                final DefaultTemporalExtent t = new DefaultTemporalExtent();
-                t.setBounds(sceneTime, sceneTime);
-                extent.setTemporalElements(singleton(t));
-                isEmpty = false;                            // Set only after the above succeed.
-            } catch (UnsupportedOperationException e) {
-                // May happen if the temporal module (which is optional) is not on the classpath.
-                warning(e);
-            }
-        }
-        return isEmpty ? null : extent;
+    private String getFilename() {
+        return (filename != null) ? filename : Vocabulary.getResources(locale).getString(Vocabulary.Keys.Unnamed);
     }
 
     /**
-     * Gets the acquisition information, or {@code null} if none. This method expects
-     * the data acquisition time in argument in order to avoid to compute it twice.
-     *
-     * @param  sceneTime  the data acquisition time, or {@code null} if none.
-     * @return the data for the Acquisition Information, or {@code null} if none.
+     * Invoked when a non-fatal exception occurred while reading metadata. This method
+     * sends a record to the registered listeners if any, or logs the record otherwise.
      */
-    private AcquisitionInformation createAcquisitionInformation(final Date sceneTime) {
-        final DefaultAcquisitionInformation acquisition = new DefaultAcquisitionInformation();
-        final DefaultPlatform platform = new DefaultPlatform();
-        String value = getValue(SPACECRAFT_ID);
-        boolean isEmpty = true;
-        if (value != null) {
-            platform.setIdentifier(new DefaultIdentifier(value));
-            isEmpty = false;
-        }
-        value = getValue(SENSOR_ID);
-        if (value != null) {
-            final DefaultInstrument instrument = new DefaultInstrument();
-            instrument.setIdentifier(new DefaultIdentifier(value));
-            platform.setInstruments(singleton(instrument));
-            isEmpty = false;
-        }
-        if (!isEmpty) {
-            acquisition.setPlatforms(singleton(platform));
-        }
-        if (sceneTime != null) {
-            final DefaultEvent event = new DefaultEvent();
-            event.setContext(Context.ACQUISITION);
-            event.setTime(sceneTime);
-            final DefaultOperation op = new DefaultOperation();
-            op.setSignificantEvents(singleton(event));
-            op.setType(OperationType.REAL);
-            op.setStatus(Progress.COMPLETED);
-            acquisition.setOperations(singleton(op));
-            isEmpty = false;
-        }
-        return isEmpty ? null : acquisition;
+    private void warning(final Exception e) {
+        listeners.warning(null, e);
     }
 
     /**
-     * Gets basic information required to uniquely identify the data, or {@code null} if none.
-     * This method expects the metadata and data acquisition time in argument in order to avoid
-     * to compute them twice.
+     * Invoked when a non-fatal error occurred while reading metadata. This method
+     * sends a record to the registered listeners if any, or logs the record otherwise.
      *
-     * @param  metadataTime  the metadata file creation time, or {@code null} if none.
-     * @param  sceneTime     the data acquisition time, or {@code null} if none.
-     * @return the data identification information, or {@code null} if none.
-     * @throws DataStoreException if a property value can not be parsed as a number or a date.
-     */
-    private Identification createIdentification(final DefaultCitationDate metadataTime, final Date sceneTime) throws DataStoreException {
-        final DefaultDataIdentification identification = new DefaultDataIdentification();
-        final DefaultCitation citation = new DefaultCitation();
-        boolean isEmpty = true;
-        if (metadataTime != null) {
-            citation.setDates(singleton(metadataTime));
-            isEmpty = false;
-        }
-        String value = getValue(LANDSAT_SCENE_ID);
-        if (value != null) {
-            citation.setIdentifiers(singleton(new DefaultIdentifier(value)));
-            isEmpty = false;
-        }
-        if (!isEmpty) {
-            identification.setCitation(citation);
-        }
-        final Extent extent = createExtent(sceneTime);
-        if (extent != null) {
-            identification.setExtents(singleton(extent));
-            isEmpty = false;
-        }
-        value = getValue(ORIGIN);
-        if (value != null) {
-            identification.setCredits(singleton(new SimpleInternationalString(value)));
-            isEmpty = false;
-        }
-        value = getValue(OUTPUT_FORMAT);
-        if (value != null) {
-            identification.setResourceFormats(singleton(new DefaultFormat(value, null)));
-            isEmpty = false;
-        }
-        return isEmpty ? null : identification;
+     * @param  error     one of the {@link Errors.Keys} values.
+     * @param  argument  the argument (or an array of arguments) to format.
+     */
+    private void warning(final short error, final Object argument) {
+        listeners.warning(errors().getString(error, argument), null);
     }
 
     /**
-     * Returns the metadata about the resources described in the Landsat file.
-     *
-     * @return the metadata about Landsat resources.
-     * @throws DataStoreException if a property value can not be parsed as a number or a date.
+     * Invoked when an expected {@code END_GROUP} statement is missing.
      */
-    public Metadata read() throws DataStoreException {
-        final DefaultMetadata metadata = new DefaultMetadata();
-        metadata.setMetadataStandards(Citations.ISO_19115);
-        final Date fileDate = getDate(FILE_DATE);
-        DefaultCitationDate metadataTime = null;
-        if (fileDate != null) {
-            metadataTime = new DefaultCitationDate(fileDate, DateType.CREATION);
-            metadata.setDateInfo(singleton(metadataTime));
-        }
-        metadata.setLanguages(singleton(Locale.ENGLISH));
-        metadata.setMetadataIdentifier(new DefaultIdentifier(getValue(LANDSAT_SCENE_ID)));
-        final Date sceneTime = getDate(DATE_ACQUIRED, SCENE_CENTER_TIME);
-        final Identification identification = createIdentification(metadataTime, sceneTime);
-        if (identification != null) {
-            metadata.setIdentificationInfo(singleton(identification));
-        }
-        final ImageDescription content = createImageDescription();
-        if (content != null) {
-            metadata.setContentInfo(singleton(content));
-        }
-        final AcquisitionInformation acquisition = createAcquisitionInformation(sceneTime);
-        if (acquisition != null) {
-            metadata.setAcquisitionInformation(singleton(acquisition));
-        }
-        metadata.setMetadataScopes(singleton(new DefaultMetadataScope(ScopeCode.DATASET, null)));
-        return metadata;
+    private void missingEndGroup(final BufferedReader reader) {
+        final Object line = (reader instanceof LineNumberReader) ? ((LineNumberReader) reader).getLineNumber() : "?";
+        listeners.warning(errors().getString(Errors.Keys.ExpectedStatementAtLine_3, "END_GROUP " + group, getFilename(), line), null);
     }
 
     /**
-     * Invoked when a non-fatal exception occurred while reading metadata. This method
-     * sends a record to the registered listeners if any, or logs the record otherwise.
+     * Returns the resources to use for formatting error messages.
      */
-    private void warning(final Exception e) {
-        listeners.warning(null, e);
+    private Errors errors() {
+        return Errors.getResources(locale);
     }
 }

Modified: sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java?rev=1759105&r1=1759104&r2=1759105&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-earth-observation/src/main/java/org/apache/sis/storage/earthobservation/LandsatStore.java [UTF-8] Sat Sep  3 16:39:34 2016
@@ -21,7 +21,6 @@ import java.io.BufferedReader;
 import java.io.LineNumberReader;
 import java.io.IOException;
 import org.opengis.metadata.Metadata;
-import org.apache.sis.metadata.ModifiableMetadata;
 import org.apache.sis.storage.DataStore;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.StorageConnector;
@@ -110,11 +109,9 @@ public class LandsatStore extends DataSt
         if (metadata == null && source != null) try {
             try (BufferedReader reader = (source instanceof BufferedReader) ? (BufferedReader) source : new LineNumberReader(source)) {
                 source = null;      // Will be closed at the end of this try-catch block.
-                final LandsatReader parser = new LandsatReader(reader, listeners);
-                metadata = parser.read();
-                if (metadata instanceof ModifiableMetadata) {
-                    ((ModifiableMetadata) metadata).freeze();
-                }
+                final LandsatReader parser = new LandsatReader(name, getLocale(), listeners);
+                parser.read(reader);
+                metadata = parser.getMetadata();
             }
         } catch (IOException e) {
             throw new DataStoreException(e);

Modified: sis/branches/JDK8/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java?rev=1759105&r1=1759104&r2=1759105&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-earth-observation/src/test/java/org/apache/sis/storage/earthobservation/LandsatReaderTest.java [UTF-8] Sat Sep  3 16:39:34 2016
@@ -17,6 +17,7 @@
 package org.apache.sis.storage.earthobservation;
 
 import java.util.Locale;
+import java.util.regex.Matcher;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -28,7 +29,7 @@ import org.apache.sis.internal.system.Mo
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
-import static org.apache.sis.test.Assert.assertMultilinesEquals;
+import static org.apache.sis.test.Assert.*;
 import static org.apache.sis.test.TestUtilities.formatNameAndValue;
 
 
@@ -42,6 +43,17 @@ import static org.apache.sis.test.TestUt
  */
 public class LandsatReaderTest extends TestCase {
     /**
+     * Tests the regular expression used for detecting the
+     * “Image courtesy of the U.S. Geological Survey” credit.
+     */
+    @Test
+    public void testCreditPattern() {
+        final Matcher m = LandsatReader.CREDIT.matcher("Image courtesy of the U.S. Geological Survey");
+        assertTrue("matches", m.find());
+        assertEquals("end", 22, m.end());
+    }
+
+    /**
      * Tests {@link LandsatReader#read()}.
      *
      * <p><b>NOTE FOR MAINTAINER:</b> if the result of this test changes, consider updating
@@ -56,7 +68,10 @@ public class LandsatReaderTest extends T
         try (BufferedReader in = new BufferedReader(new InputStreamReader(
                 LandsatReaderTest.class.getResourceAsStream("LandsatTest.txt"), "UTF-8")))
         {
-            actual = new LandsatReader(in, new EmptyWarningListeners<>(Locale.US, Modules.EARTH_OBSERVATION)).read();
+            final LandsatReader reader = new LandsatReader("LandsatTest.txt", Locale.ENGLISH,
+                    new EmptyWarningListeners<>(Locale.ENGLISH, Modules.EARTH_OBSERVATION));
+            reader.read(in);
+            actual = reader.getMetadata();
         }
         final String text = formatNameAndValue(DefaultMetadata.castOrCopy(actual).asTreeTable());
         assertMultilinesEquals(

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=1759105&r1=1759104&r2=1759105&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 Sep  3 16:39:34 2016
@@ -100,7 +100,7 @@ public class GeoTiffStore extends DataSt
             while ((dir = reader.getImageFileDirectory(n++)) != null) {
                 dir.completeMetadata(reader.metadata, locale);
             }
-            metadata = reader.metadata.result();
+            metadata = reader.metadata.build(true);
         } catch (IOException e) {
             throw new DataStoreException(reader.errors().getString(Errors.Keys.CanNotRead_1, reader.input.filename), e);
         } catch (ArithmeticException e) {



Mime
View raw message