sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1405319 - in /sis/branches/JDK7/sis-utility/src: main/java/org/apache/sis/internal/simple/ main/java/org/apache/sis/internal/util/ main/java/org/apache/sis/measure/ test/java/org/apache/sis/measure/ test/java/org/apache/sis/test/suite/
Date Sat, 03 Nov 2012 11:03:26 GMT
Author: desruisseaux
Date: Sat Nov  3 11:03:25 2012
New Revision: 1405319

URL: http://svn.apache.org/viewvc?rev=1405319&view=rev
Log:
Implemented AngleFormat.formatToCharacterIterator(Object).

Added:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SurjectiveConverter.java
  (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormattedCharacterIterator.java
  (with props)
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/FormattedCharacterIteratorTest.java
  (with props)
Modified:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/simple/SimpleCharacterIterator.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java
    sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/simple/SimpleCharacterIterator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/simple/SimpleCharacterIterator.java?rev=1405319&r1=1405318&r2=1405319&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/simple/SimpleCharacterIterator.java
(original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/simple/SimpleCharacterIterator.java
Sat Nov  3 11:03:25 2012
@@ -51,8 +51,10 @@ public class SimpleCharacterIterator imp
 
     /**
      * The upper index (index after the last character that we can return).
+     * This field is not final because some classes need to update it, for
+     * example if {@link #text} is a growing {@link StringBuffer}.
      */
-    protected final int upper;
+    protected int upper;
 
     /**
      * The index of the next character to be returned by the iterator.
@@ -198,6 +200,6 @@ public class SimpleCharacterIterator imp
      */
     @Override
     public final String toString() {
-        return text.toString();
+        return text.subSequence(lower, upper).toString();
     }
 }

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SurjectiveConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SurjectiveConverter.java?rev=1405319&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SurjectiveConverter.java
(added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SurjectiveConverter.java
Sat Nov  3 11:03:25 2012
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.util;
+
+import java.util.Set;
+import java.util.EnumSet;
+import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.math.FunctionProperty;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * Base class for non-invertible surjective {@link ObjectConverter}s.
+ * Surjective converters are converters for which many different source values can produce
+ * the same target value. In many cases, the target value having many possible sources is
+ * the {@code null} value. This is the case in particular when the converter is used as a
+ * filter.
+ *
+ * <p>This base class is stateless. Consequently sub-classes that choose to implement
+ * {@link java.io.Serializable} do not need to care about this base class.</p>
+ *
+ * @param <S> The type of objects to convert.
+ * @param <T> The type of converted objects.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public abstract class SurjectiveConverter<S,T> implements ObjectConverter<S,T>
{
+    /**
+     * Creates a new converter.
+     */
+    protected SurjectiveConverter() {
+    }
+
+    /**
+     * Returns the function property, which contains only {@link FunctionProperty#SURJECTIVE}.
+     */
+    @Override
+    public Set<FunctionProperty> properties() {
+        return EnumSet.of(FunctionProperty.SURJECTIVE);
+    }
+
+    /**
+     * Unsupported operation, since surjective converters are non-invertible.
+     */
+    @Override
+    public ObjectConverter<T,S> inverse() throws UnsupportedOperationException {
+        throw new UnsupportedOperationException(Errors.format(
+                Errors.Keys.UnsupportedOperation_1, "inverse"));
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SurjectiveConverter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SurjectiveConverter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java?rev=1405319&r1=1405318&r2=1405319&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/AngleFormat.java Sat
Nov  3 11:03:25 2012
@@ -24,6 +24,7 @@ import java.text.ParseException;
 import java.text.NumberFormat;
 import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
+import java.text.AttributedCharacterIterator;
 import net.jcip.annotations.NotThreadSafe;
 
 import org.apache.sis.util.Debug;
@@ -222,6 +223,19 @@ public class AngleFormat extends Format 
          * constructor in order to get the bounding index where hemisphere have been written.
          */
         public static final Field HEMISPHERE = new Field("HEMISPHERE", HEMISPHERE_FIELD);
+
+        /**
+         * Returns the field constant for the given numeric identifier.
+         */
+        static Field forCode(final int field) {
+            switch (field) {
+                case DEGREES_FIELD:    return DEGREES;
+                case MINUTES_FIELD:    return MINUTES;
+                case SECONDS_FIELD:    return SECONDS;
+                case HEMISPHERE_FIELD: return HEMISPHERE;
+                default: throw new AssertionError(field);
+            }
+        }
     }
 
     /**
@@ -278,12 +292,22 @@ public class AngleFormat extends Format 
     private transient NumberFormat numberFormat;
 
     /**
-     * Object to give to {@code DecimalFormat.format} methods,
-     * cached in order to avoid recreating this object too often.
+     * Object to give to {@code DecimalFormat.format} methods in order to get the position
+     * of the integer part. Cached in order to avoid recreating this object too often.
+     *
+     * @see #integerFieldPosition()
+     */
+    private transient FieldPosition integerFieldPosition;
+
+    /**
+     * A temporary variable which may be set to the character iterator for which the
+     * attributes need to be set. IF non-null, then this is actually an instance of
+     * {@link FormattedCharacterIterator}. But we use the interface here for avoiding
+     * too early class loading.
      *
-     * @see #dummyFieldPosition()
+     * @see #formatToCharacterIterator(Object)
      */
-    private transient FieldPosition dummy;
+    private transient AttributedCharacterIterator characterIterator;
 
     /**
      * Returns the number format, created when first needed.
@@ -296,13 +320,13 @@ public class AngleFormat extends Format 
     }
 
     /**
-     * Returns the dummy field position.
+     * Returns the field position for the integer part of numeric values.
      */
-    private FieldPosition dummyFieldPosition() {
-        if (dummy == null) {
-            dummy = new FieldPosition(-1);
+    private FieldPosition integerFieldPosition() {
+        if (integerFieldPosition == null) {
+            integerFieldPosition = new FieldPosition(NumberFormat.INTEGER_FIELD);
         }
-        return dummy;
+        return integerFieldPosition;
     }
 
     /**
@@ -779,7 +803,7 @@ scan:   for (int i=0; i<length;) {
         final int offset = toAppendTo.length();
         final int fieldPos = getField(pos);
         if (isNaN(angle) || isInfinite(angle)) {
-            toAppendTo = numberFormat().format(angle, toAppendTo, dummyFieldPosition());
+            toAppendTo = numberFormat().format(angle, toAppendTo, integerFieldPosition());
             if (fieldPos >= DEGREES_FIELD && fieldPos <= SECONDS_FIELD) {
                 pos.setBeginIndex(offset);
                 pos.setEndIndex(toAppendTo.length());
@@ -852,7 +876,7 @@ scan:   for (int i=0; i<length;) {
             if (hasMore) {
                 numberFormat.setMinimumIntegerDigits(width);
                 numberFormat.setMaximumFractionDigits(0);
-                toAppendTo = numberFormat.format(value, toAppendTo, dummyFieldPosition());
+                toAppendTo = numberFormat.format(value, toAppendTo, integerFieldPosition());
             } else if (useDecimalSeparator) {
                 numberFormat.setMinimumIntegerDigits(width);
                 if (maximumTotalWidth != 0) {
@@ -875,19 +899,25 @@ scan:   for (int i=0; i<length;) {
                 }
                 numberFormat.setMinimumFractionDigits(minimumFractionDigits);
                 numberFormat.setMaximumFractionDigits(maximumFractionDigits);
-                toAppendTo = numberFormat.format(value, toAppendTo, dummyFieldPosition());
+                toAppendTo = numberFormat.format(value, toAppendTo, integerFieldPosition());
             } else {
                 value *= pow10(fractionFieldWidth);
                 numberFormat.setMaximumFractionDigits(0);
                 numberFormat.setMinimumIntegerDigits(width + fractionFieldWidth);
-                toAppendTo = numberFormat.format(value, toAppendTo, dummyFieldPosition());
+                toAppendTo = numberFormat.format(value, toAppendTo, integerFieldPosition());
             }
             if (suffix != null) {
                 toAppendTo.append(suffix);
             }
             if (field == fieldPos) {
                 pos.setBeginIndex(startPosition);
-                pos.setEndIndex(toAppendTo.length() - 1);
+                pos.setEndIndex(toAppendTo.length());
+            }
+            if (characterIterator instanceof FormattedCharacterIterator) {
+                final Integer integerPart = Integer.valueOf((int) value);
+                final FormattedCharacterIterator it = (FormattedCharacterIterator) characterIterator;
+                it.addField(Field.forCode(field), integerPart, startPosition, toAppendTo.length());
+                it.addField(NumberFormat.Field.INTEGER, integerPart, integerFieldPosition);
             }
             field++;
         } while (hasMore);
@@ -950,16 +980,57 @@ scan:   for (int i=0; i<length;) {
             final FieldPosition pos, final char positiveSuffix, final char negativeSuffix)
     {
         toAppendTo = format(abs(angle), toAppendTo, pos);
-        final int start = toAppendTo.length();
-        toAppendTo.append(isNegative(angle) ? negativeSuffix : positiveSuffix);
+        final int startPosition = toAppendTo.length();
+        final char suffix = isNegative(angle) ? negativeSuffix : positiveSuffix;
+        toAppendTo.append(suffix);
         if (getField(pos) == HEMISPHERE_FIELD) {
-            pos.setBeginIndex(start);
-            pos.setEndIndex(toAppendTo.length()-1);
+            pos.setBeginIndex(startPosition);
+            pos.setEndIndex(toAppendTo.length());
+        }
+        if (characterIterator instanceof FormattedCharacterIterator) {
+            ((FormattedCharacterIterator) characterIterator).addField(
+                    Field.HEMISPHERE, suffix, startPosition, toAppendTo.length());
         }
         return toAppendTo;
     }
 
     /**
+     * Formats an angle, latitude or longitude value as an attributed character iterator.
+     * Callers can iterator over the returned iterator and queries the attribute values as
+     * in the following example:
+     *
+     * {@preformat java
+     *     AttributedCharacterIterator it = angleFormat.formatToCharacterIterator(myAngle);
+     *     for (char c=it.first(); c!=AttributedCharacterIterator.DONE; c=c.next()) {
+     *         // 'c' is a character from the formatted string.
+     *         if (it.getAttribute(AngleFormat.Field.MINUTES) != null) {
+     *             // If we enter this block, then the character 'c' is part of the minutes
field,
+     *             // This field extends from it.getRunStart(MINUTES) to it.getRunLimit(MINUTES).
+     *         }
+     *     }
+     * }
+     *
+     * In Apache SIS implementation, the returned character iterator also implements the
+     * {@link CharSequence} interface for convenience.
+     *
+     * @param  value {@link Angle} object to format.
+     * @return A character iterator together with the attributes describing the formatted
value.
+     * @throws IllegalArgumentException if {@code value} if not an instance of {@link Angle}.
+     */
+    @Override
+    public AttributedCharacterIterator formatToCharacterIterator(final Object value) {
+        final StringBuffer buffer = new StringBuffer(20);
+        final FormattedCharacterIterator it = new FormattedCharacterIterator(buffer);
+        try {
+            characterIterator = it;
+            format(value, buffer, null);
+        } finally {
+            characterIterator = null;
+        }
+        return it;
+    }
+
+    /**
      * Ignores a field suffix, then returns the identifier of the suffix just skipped.
      * This method is invoked by {@link #parse(String, ParsePosition)} for determining
      * what was the field it just parsed. For example if we just parsed "48°12'", then
@@ -1512,7 +1583,7 @@ BigBoss:    switch (skipSuffix(source, p
     public AngleFormat clone() {
         final AngleFormat clone = (AngleFormat) super.clone();
         clone.numberFormat = null;
-        clone.dummy = null;
+        clone.integerFieldPosition = null;
         return clone;
     }
 

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java?rev=1405319&r1=1405318&r2=1405319&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormatField.java Sat
Nov  3 11:03:25 2012
@@ -28,13 +28,19 @@ import java.text.Format;
  * @version 0.3
  * @module
  */
-abstract class FormatField extends Format.Field {
+class FormatField extends Format.Field {
     /**
      * Serial number for cross-version compatibility.
      */
     private static final long serialVersionUID = -5344437554919766622L;
 
     /**
+     * A sentinel value for {@link FormattedCharacterIterator} internal usage only,
+     * meaning that all attributes shall be taken in account while computing a run range.
+     */
+    static final FormatField ALL = new FormatField("ALL", 0);
+
+    /**
      * The numeric {@code *_FIELD} value for this constant. This value doesn't need to
      * be serialized, because {@link #readResolve()} will locate the original constant
      * on deserialization.

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormattedCharacterIterator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormattedCharacterIterator.java?rev=1405319&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormattedCharacterIterator.java
(added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormattedCharacterIterator.java
Sat Nov  3 11:03:25 2012
@@ -0,0 +1,408 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.measure;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.text.Format;
+import java.text.FieldPosition;
+import java.text.AttributedCharacterIterator;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.internal.util.SurjectiveConverter;
+import org.apache.sis.internal.simple.SimpleCharacterIterator;
+
+import static org.apache.sis.util.collection.Collections.derivedMap;
+
+
+/**
+ * The attributed character iterator to be returned by {@link Format}
+ * implementations in the {@code org.apache.sis.measure} package.
+ *
+ * {@section Implementation assumption}
+ * Every {@code getRunStart(…)} and {@code getRunLimit(…)} methods defined in this
class check
+ * only for attribute existence, ignoring the actual attribute value. This is a departure
from
+ * the {@link java.text.AttributedCharacterIterator} contract, but should be invisible to
the
+ * users if there is no juxtaposed fields with the same attribute value (which is usually
the
+ * case). A violation occurs if different fields are formatted without separator. For example
+ * if an angle is formatted as "DDMMSS" without any field separator, then we have 3 juxtaposed
+ * integer fields. If those fields have the same value, then the whole "DDMMSS" text should
be
+ * seen as a single run according the {@code AttributedCharacterIterator} contract, while
they
+ * will still been seen as 3 separated fields by this implementation.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+final class FormattedCharacterIterator extends SimpleCharacterIterator implements AttributedCharacterIterator
{
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 360841752298264186L;
+
+    /**
+     * Holds a field value, together with the run range in which this value is valid.
+     * Contains also a reference to the previous {@code Entry} in order to build a chained
+     * list (in reverse of insertion order) if many values exist for the same field.
+     *
+     * <p>To be more specific:</p>
+     * <ul>
+     *   <li>The map key is one of the static constants defined in the formatter {@code
Field} inner class.</li>
+     *   <li>{@link #value} is the numeric value being formatted for that particular
field.</li>
+     *   <li>{@link #start} and {@link #limit} are the range of index in the
+     *       {@link SimpleCharacterIterator#text} where the field value has been formatted.</li>
+     * </ul>
+     *
+     * <b>Example:</b> if {@link AngleFormat} formats "10°30′" and the
user wants information
+     * about the degrees field, then:
+     *
+     * <ul>
+     *   <li>The map key is {@link AngleFormat.Field#DEGREES};</li>
+     *   <li>{@link #value} is {@code Double.valueOf(10)};</li>
+     *   <li>{@link #start} is 0;</li>
+     *   <li>{@link #limit} is 3.</li>
+     * </ul>
+     */
+    private static final class Entry {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = 3297480138621390486L;
+
+        /**
+         * The attribute value.
+         */
+        final Object value;
+
+        /**
+         * The range of index in {@link SimpleCharacterIterator#text} where the value has
+         * been formatted. See class javadoc for more information.
+         */
+        final int start, limit;
+
+        /**
+         * The previous entry in a chained list of entries for the same key, or {@code null}
if none.
+         */
+        final Entry previous;
+
+        /**
+         * Creates a new entry for the given value, together with the range of index where
+         * the field value has been formatted. See class javadoc for more information.
+         */
+        Entry(final Attribute field, final Object value, final int start, final int limit,
+                final Map<Attribute,Entry> attributes)
+        {
+            this.value = value;
+            this.start = start;
+            this.limit = limit;
+            previous = attributes.put(field, this);
+        }
+    }
+
+    /**
+     * All fields associated to the {@linkplain #text text}.
+     *
+     * <p>This map shall not be modified after this {@code FormattedCharacterIterator}
become
+     * visible to the user. If this map could be modified, then we would need to override
the
+     * {@link #clone()} method in order to clone this map too.</p>
+     */
+    private final Map<Attribute,Entry> attributes;
+
+    /**
+     * An unmodifiable view over the keys of the {@link #attributes} map.
+     * This view is created when first needed.
+     *
+     * @see #getAllAttributeKeys()
+     */
+    private transient Set<Attribute> attributeKeys;
+
+    /**
+     * The attribute given in the last call to a {@code getRunStart(…)} or {@code getRunLimit(…)}
+     * method. Used for determining if the {@link #start} and {@link #limit} fields need
a update.
+     * A {@code null} value means that the run range needs an unconditional update (this
is the
+     * case after construction and after deserialization).
+     */
+    private transient Attribute runAttribute;
+
+    /**
+     * The value to be returned by {@code getRunStart(…)} and {@code getRunLimit(…)}
+     * when the index value is {@code validity}. Those values are updated when needed
+     * by the {@link #update(Set)} method.
+     */
+    private transient int start, limit, validity;
+
+    /**
+     * Creates a new character iterator for the given character sequence.
+     *
+     * @param text The formatted text. Can be a {@link StringBuilder} to be filled later.
+     */
+    FormattedCharacterIterator(final CharSequence text) {
+        super(text);
+        attributes = new IdentityHashMap<>(8);
+    }
+
+    /**
+     * Adds the run range, together with the value, for a given field in the formatted text.
+     * Callers shall avoid adding the same (<var>field</var>, <var>value</var>)
pair in two
+     * or more juxtaposed fields if possible, since it may cause a violation of the
+     * {@link java.text.AttributedCharacterIterator} contract in current implementation.
+     * See the class javadoc for details.
+     */
+    final void addField(final Attribute field, final Object value, final int start, final
int limit) {
+        upper = text.length(); // Update for new charaters added in the StringBuffer since
last call.
+        assert (start >= lower) && (limit >= start) && (limit <=
upper) : limit;
+        Entry e = new Entry(field, value, start, limit, attributes); // Constructor adds
itself to the map.
+        assert ((e = e.previous) == null) || (start >= e.limit); // Check for non-overlapping
fields.
+    }
+
+    /**
+     * Same as {@link #addField(Attribute, Object, int, int)}, but extracting the {@code
start} and
+     * {@code limit} values from the given {@link FieldPosition}. This is a convenience method
for
+     * the formatters which delegate part of their work to another formatter.
+     */
+    final void addField(final Attribute field, final Object value, final FieldPosition pos)
{
+        addField(field, value, pos.getBeginIndex(), pos.getEndIndex());
+    }
+
+    /**
+     * Ensures that the {@link #start}, {@link #limit} and {@link #attributes} fields
+     * are valid for the current index position and the given attribute.
+     *
+     * @param attribute The attribute which shall have the same value in the run range.
+     * @param entries   The entries on which to iterate for computing the run range.
+     *                  Mandatory if {@code attribute} is {@code null}.
+     */
+    private void update(final Attribute attribute, Collection<Entry> entries) {
+        final int index = getIndex();
+        if (attribute == null || attribute != runAttribute || index != validity) {
+            runAttribute = attribute;
+            validity     = index;
+            start        = lower;
+            limit        = upper;
+            if (entries == null) {
+                if (attribute == FormatField.ALL) {
+                    entries = attributes.values();
+                } else {
+                    entries = Collections.singleton(attributes.get(attribute));
+                }
+            }
+            for (Entry entry : entries) {
+                while (entry != null) {
+                    if (index >= entry.start && index < entry.limit) {
+                        if (entry.start > start) start = entry.start;
+                        if (entry.limit < limit) limit = entry.limit;
+                    }
+                    entry = entry.previous;
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the index of the first character of the run having all the same attributes
+     * than the current character. See this class javadoc for a note about which attributes
+     * are considered equal.
+     */
+    @Override
+    public int getRunStart() {
+        update(FormatField.ALL, null);
+        return start;
+    }
+
+    /**
+     * Returns the index of the first character of the run having the same "value" for the
+     * given attribute than the current character. See this class javadoc for a note about
+     * which attribute "values" are considered equal.
+     */
+    @Override
+    public int getRunStart(final Attribute attribute) {
+        ArgumentChecks.ensureNonNull("attribute", attribute);
+        update(attribute, null);
+        return start;
+    }
+
+    /**
+     * Returns the index of the first character of the run having the same "values" for the
+     * given attributes than the current character. See this class javadoc for a note about
+     * which attribute "values" are considered equal.
+     */
+    @Override
+    public int getRunStart(final Set<? extends Attribute> attributes) {
+        update(null, entries(attributes));
+        return start;
+    }
+
+    /**
+     * Returns the index of the first character following the run having all the same attributes
+     * than the current character. See this class javadoc for a note about which attributes
are
+     * considered equal.
+     */
+    @Override
+    public int getRunLimit() {
+        update(FormatField.ALL, null);
+        return limit;
+    }
+
+    /**
+     * Returns the index of the first character following the run having the same "value"
for
+     * the given attribute than the current character. See this class javadoc for a note
about
+     * which attribute "values" are considered equal.
+     */
+    @Override
+    public int getRunLimit(final Attribute attribute) {
+        ArgumentChecks.ensureNonNull("attribute", attribute);
+        update(attribute, null);
+        return limit;
+    }
+
+    /**
+     * Returns the index of the first character following the run having the same "values"
for
+     * the given attributes than the current character. See this class javadoc for a note
about
+     * which attribute "values" are considered equal.
+     */
+    @Override
+    public int getRunLimit(final Set<? extends Attribute> attributes) {
+        update(null, entries(attributes));
+        return limit;
+    }
+
+    /**
+     * Returns the entries for the given attributes. This is a helper method for the
+     * {@code getRunStart(Set)} and {@code getRunLimit(Set)} methods.
+     */
+    private Collection<Entry> entries(final Set<? extends Attribute> requested)
{
+        final Collection<Entry> entries = new ArrayList<>(requested.size());
+        for (final Attribute r : requested) {
+            final Entry e = attributes.get(r);
+            if (e != null) {
+                entries.add(e);
+            }
+        }
+        return entries;
+    }
+
+    /**
+     * The object converter to use for extracting {@link Entry#value} in the map returned
+     * by {@link FormattedCharacterIterator#getAttributes()}. The value to extract depends
+     * on the character index.
+     */
+    private static final class Selector extends SurjectiveConverter<Entry,Object> implements
Serializable {
+        private static final long serialVersionUID = -7281235148346378214L;
+
+        /** Index of the character for which the map of attributes is requested. */
+        private final int index;
+
+        /** Creates a new value converter for the character at the given index. */
+        Selector(final int index) {
+            this.index = index;
+        }
+
+        /** Returns the value for the given entry, or {@code null} if none. */
+        @Override
+        public Object convert(Entry entry) {
+            while (entry != null) {
+                if (index >= entry.start && index < entry.limit) {
+                    return entry.value;
+                }
+                entry = entry.previous;
+            }
+            return null;
+        }
+
+        @Override public Class<Entry>  getSourceClass()  {return Entry.class;}
+        @Override public Class<Object> getTargetClass()  {return Object.class;}
+    }
+
+    /**
+     * The object converter to use for filtering the keys in the map returned by
+     * {@link FormattedCharacterIterator#getAttributes()}.
+     */
+    private static class Filter extends SurjectiveConverter<Attribute,Attribute> implements
Serializable {
+        private static final long serialVersionUID = 6951804952836918035L;
+
+        /** A reference to {@link FormattedCharacterIterator#attributes}. */
+        private final Map<Attribute,Entry> attributes;
+
+        /** Index of the character for which the map of attribute is requested. */
+        private final int index;
+
+        /** Creates a new key filter for the character at the given index. */
+        Filter(final Map<Attribute,Entry> attributes, final int index) {
+            this.attributes = attributes;
+            this.index = index;
+        }
+
+        /** Returns {@code attribute} if it shall be included in the derived map, or {@code
null} otherwise. */
+        @Override
+        public Attribute convert(final Attribute attribute) {
+            for (Entry e=attributes.get(attribute); e!=null; e=e.previous) {
+                if (index >= e.start && index < e.limit) {
+                    return attribute;
+                }
+            }
+            return null;
+        }
+
+        @Override public Class<Attribute> getSourceClass()  {return Attribute.class;}
+        @Override public Class<Attribute> getTargetClass()  {return Attribute.class;}
+    }
+
+    /**
+     * Returns the attributes defined on the current character.
+     */
+    @Override
+    public Map<Attribute, Object> getAttributes() {
+        final int index = getIndex();
+        return derivedMap(attributes, new Filter(attributes, index), new Selector(index));
+    }
+
+    /**
+     * Returns the value of the named attribute for the current character, or {@code null}
if none.
+     */
+    @Override
+    public Object getAttribute(final Attribute attribute) {
+        final int index = getIndex();
+        for (Entry e=attributes.get(attribute); e!=null; e=e.previous) {
+            if (index >= e.start && index < e.limit) {
+                return e.value;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the keys of all attributes defined in the iterator text range.
+     */
+    @Override
+    public Set<Attribute> getAllAttributeKeys() {
+        if (attributeKeys == null) {
+            attributeKeys = Collections.unmodifiableSet(attributes.keySet());
+        }
+        return attributeKeys;
+    }
+
+    /*
+     * We do not override the clone() method because the 'attributes' map will not
+     * be modified after this FormattedCharacterIterator become visible to the user,
+     * so we don't need to clone the map.
+     */
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormattedCharacterIterator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/FormattedCharacterIterator.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java?rev=1405319&r1=1405318&r2=1405319&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java
(original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/AngleFormatTest.java
Sat Nov  3 11:03:25 2012
@@ -17,6 +17,8 @@
 package org.apache.sis.measure;
 
 import java.util.Locale;
+import java.text.FieldPosition;
+import java.text.AttributedCharacterIterator;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
@@ -34,7 +36,10 @@ import static org.apache.sis.test.TestUt
  * @version 0.3
  * @module
  */
-@DependsOn(org.apache.sis.math.MathFunctionsTest.class)
+@DependsOn({
+  FormattedCharacterIteratorTest.class,
+  org.apache.sis.math.MathFunctionsTest.class
+})
 public final strictfp class AngleFormatTest extends TestCase {
     /**
      * Tests a pattern with illegal usage of D, M and S symbols.
@@ -197,4 +202,44 @@ public final strictfp class AngleFormatT
         assertEquals("8°",  f.format(new Angle( 8.123456)));
         assertEquals("20°", f.format(new Angle(20.123456)));
     }
+
+    /**
+     * Tests the field position while formatting an angle.
+     */
+    @Test
+    public void testFieldPosition() {
+        final Latitude latitude = new Latitude(FormattedCharacterIteratorTest.LATITUDE_VALUE);
+        final AngleFormat f = new AngleFormat("DD°MM′SS.s″", Locale.CANADA);
+        final StringBuffer buffer = new StringBuffer(12);
+        for (int i=AngleFormat.DEGREES_FIELD; i<=AngleFormat.HEMISPHERE_FIELD; i++) {
+            final AngleFormat.Field field;
+            final int start, limit;
+            switch (i) {
+                case AngleFormat.DEGREES_FIELD:    field = AngleFormat.Field.DEGREES;   
start= 0; limit= 3;  break;
+                case AngleFormat.MINUTES_FIELD:    field = AngleFormat.Field.MINUTES;   
start= 3; limit= 6;  break;
+                case AngleFormat.SECONDS_FIELD:    field = AngleFormat.Field.SECONDS;   
start= 6; limit=11; break;
+                case AngleFormat.HEMISPHERE_FIELD: field = AngleFormat.Field.HEMISPHERE;
start=11; limit=12; break;
+                default: continue; // Skip the fraction field.
+            }
+            final FieldPosition pos = new FieldPosition(field);
+            assertEquals(FormattedCharacterIteratorTest.LATITUDE_STRING, f.format(latitude,
buffer, pos).toString());
+            assertSame  ("getFieldAttribute", field, pos.getFieldAttribute());
+            assertEquals("getBeginIndex",     start, pos.getBeginIndex());
+            assertEquals("getEndIndex",       limit, pos.getEndIndex());
+            buffer.setLength(0);
+        }
+    }
+
+    /**
+     * Tests the {@link AngleFormat#formatToCharacterIterator(Object)} method.
+     */
+    @Test
+    @DependsOnMethod("testFieldPosition")
+    public void testFormatToCharacterIterator() {
+        final Latitude latitude = new Latitude(FormattedCharacterIteratorTest.LATITUDE_VALUE);
+        final AngleFormat f = new AngleFormat("DD°MM′SS.s″", Locale.CANADA);
+        final AttributedCharacterIterator it = f.formatToCharacterIterator(latitude);
+        assertEquals(FormattedCharacterIteratorTest.LATITUDE_STRING, it.toString());
+        FormattedCharacterIteratorTest.testAttributes(it, true);
+    }
 }

Added: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/FormattedCharacterIteratorTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/FormattedCharacterIteratorTest.java?rev=1405319&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/FormattedCharacterIteratorTest.java
(added)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/FormattedCharacterIteratorTest.java
Sat Nov  3 11:03:25 2012
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.measure;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
+import java.text.AttributedCharacterIterator;
+import org.apache.sis.test.TestCase;
+import org.apache.sis.test.DependsOnMethod;
+import org.junit.Test;
+
+import static java.text.NumberFormat.Field.INTEGER;
+import static java.text.AttributedCharacterIterator.DONE;
+import static java.text.AttributedCharacterIterator.Attribute;
+import static org.apache.sis.measure.AngleFormat.Field.*;
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests the {@link FormattedCharacterIterator} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3
+ * @version 0.3
+ * @module
+ */
+public final strictfp class FormattedCharacterIteratorTest extends TestCase {
+    /**
+     * The string used for testing purpose in this class.
+     */
+    static final String LATITUDE_STRING = "45°30′15.0″N";
+
+    /**
+     * The numerical value corresponding to {@link #ANGLE_STRING}.
+     * This information is used by {@link AngleFormatTest}.
+     */
+    static final double LATITUDE_VALUE = 45.50416666666667;
+
+    /**
+     * Tests an iteration without attribute.
+     */
+    @Test
+    public void testWithoutAttribute() {
+        final String text = LATITUDE_STRING;
+        final FormattedCharacterIterator it = new FormattedCharacterIterator(text);
+        assertTrue(it.getAllAttributeKeys().isEmpty());
+        assertEquals(text, it.toString());
+
+        int i = 0;
+        for (char c=it.first(); c!=DONE; c=it.next()) {
+            assertEquals(text.charAt(i), c);
+            assertEquals(it  .charAt(i), c);
+            assertEquals("getIndex",    i++,           it.getIndex());
+            assertEquals("getRunStart", 0,             it.getRunStart());
+            assertEquals("getRunLimit", text.length(), it.getRunLimit());
+            assertTrue(it.getAttributes().isEmpty());
+        }
+        assertEquals(text.length(), i);
+        for (char c=it.last(); c!=DONE; c=it.previous()) {
+            assertEquals(text.charAt(--i), c);
+            assertEquals(it  .charAt(  i), c);
+            assertEquals("getIndex",    i,             it.getIndex());
+            assertEquals("getRunStart", 0,             it.getRunStart());
+            assertEquals("getRunLimit", text.length(), it.getRunLimit());
+            assertTrue(it.getAttributes().isEmpty());
+        }
+        assertEquals(0, i);
+    }
+
+    /**
+     * Tests an iteration with {@code DEGREES}/{@code MINUTES}/{@code SECONDS} attributes.
+     */
+    @Test
+    public void testNonOverlappingAttributes() {
+        testAttributes(false);
+    }
+
+    /**
+     * Tests an iteration with {@code INTEGER} attributes in addition to the
+     * {@code DEGREES}/{@code MINUTES}/{@code SECONDS} ones. The {@code INTEGER}
+     * attributes are defined on sub-range of the degrees/minutes/seconds ones.
+     */
+    @Test
+    @DependsOnMethod("testNonOverlappingAttributes")
+    public void testOverlappingAttributes() {
+        testAttributes(true);
+    }
+
+    /**
+     * Returns all expected attribute keys for the tests in this class.
+     *
+     * @param overlapping {@code true} for including the keys for overlapping attributes.
+     */
+    private static Set<Attribute> getAllAttributeKeys(final boolean overlapping) {
+        final Set<Attribute> keys = new HashSet<>(8);
+        assertTrue(keys.add(DEGREES));
+        assertTrue(keys.add(MINUTES));
+        assertTrue(keys.add(SECONDS));
+        assertTrue(keys.add(HEMISPHERE));
+        if (overlapping) {
+            assertTrue(keys.add(INTEGER));
+        }
+        return keys;
+    }
+
+    /**
+     * Tests an iteration with attributes, optionally having {@code INTEGER} attributes
+     * overlapping the {@code DEGREES}/{@code MINUTES}/{@code SECONDS} ones.
+     */
+    private static void testAttributes(final boolean overlapping) {
+        final FormattedCharacterIterator it = new FormattedCharacterIterator(LATITUDE_STRING);
+        it.addField(DEGREES,     45,  0,  3);
+        it.addField(MINUTES,     30,  3,  6);
+        it.addField(SECONDS,     15,  6, 11);
+        it.addField(HEMISPHERE, 'N', 11, 12);
+        if (overlapping) {
+            it.addField(INTEGER, 45,  0,  2);
+            it.addField(INTEGER, 30,  3,  5);
+            it.addField(INTEGER, 15,  6,  8);
+        }
+        testAttributes(it, overlapping);
+    }
+
+    /**
+     * Tests an iteration with attributes, optionally having {@code INTEGER} attributes
+     * overlapping the {@code DEGREES}/{@code MINUTES}/{@code SECONDS} ones. The given
+     * iterator shall iterates over the {@code "45°30′15.0″N"} characters.
+     *
+     * <p>This test is leveraged by {@link AngleFormatTest#testFormatToCharacterIterator()}.</p>
+     */
+    @SuppressWarnings("fallthrough")
+    static void testAttributes(final AttributedCharacterIterator it, final boolean overlapping)
{
+        assertEquals(getAllAttributeKeys(overlapping), it.getAllAttributeKeys());
+        for (char c=it.first(); c!=DONE; c=it.next()) {
+            final AngleFormat.Field key;
+            final Comparable<?> value;
+            final int start, limit;
+            int oi = 0; // How many characters to remove for having the limit of the integer
field.
+            switch (it.getIndex()) {
+                case  0: assertEquals('4', c); // Beginning of 45°
+                case  1: oi = overlapping ? 1 : 0;
+                case  2: key=DEGREES;    value= 45; start=0; limit= 3; break;
+                case  3: assertEquals('3', c); // Beginning of 30′
+                case  4: oi = overlapping ? 1 : 0;
+                case  5: key=MINUTES;    value= 30; start=3; limit= 6; break;
+                case  6: assertEquals('1', c); // Beginning of 15.0″
+                case  7: oi = overlapping ? 3 : 0;
+                case  8:
+                case  9:
+                case 10: key=SECONDS;    value= 15; start= 6; limit=11; break;
+                case 11: key=HEMISPHERE; value='N'; start=11; limit=12; break;
+                default: throw new AssertionError();
+            }
+            final Map<Attribute,Object> attributes = it.getAttributes();
+            assertEquals("attributes.size", (oi!=0) ? 2     : 1,    attributes.size());
+            assertEquals("attributes.get",            value,        attributes.get(key));
+            assertEquals("attributes.get",  (oi!=0) ? value : null, attributes.get(INTEGER));
+
+            assertEquals("getRunStart",           start,         it.getRunStart());
+            assertEquals("getRunLimit",           limit-oi,      it.getRunLimit());
+            assertEquals("getRunStart",           start,         it.getRunStart(key));
+            assertEquals("getRunLimit",           limit,         it.getRunLimit(key));
+            assertEquals("getRunStart", (oi!=0) ? start    :  0, it.getRunStart(INTEGER));
+            assertEquals("getRunLimit", (oi!=0) ? limit-oi : 12, it.getRunLimit(INTEGER));
+        }
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/FormattedCharacterIteratorTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/measure/FormattedCharacterIteratorTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1405319&r1=1405318&r2=1405319&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
(original)
+++ sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
Sat Nov  3 11:03:25 2012
@@ -58,6 +58,7 @@ import org.junit.runners.Suite;
   org.apache.sis.util.type.DefaultInternationalStringTest.class,
 
   // Formatting
+  org.apache.sis.measure.FormattedCharacterIteratorTest.class,
   org.apache.sis.measure.AngleFormatTest.class,
   org.apache.sis.measure.AngleTest.class,
   org.apache.sis.internal.util.X364Test.class,



Mime
View raw message