Author: rwhitcomb
Date: Mon Feb 15 21:55:20 2021
New Revision: 1886550

URL: http://svn.apache.org/viewvc?rev=1886550&view=rev
Log:
Do some code cleanup in CharSpan; add functionality there; add a test module 
for it.

Added:
    pivot/trunk/core/test/org/apache/pivot/text/
    pivot/trunk/core/test/org/apache/pivot/text/test/
    pivot/trunk/core/test/org/apache/pivot/text/test/CharSpanTest.java
Modified:
    pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java

Modified: pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java
URL: 
http://svn.apache.org/viewvc/pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java?rev=1886550&r1=1886549&r2=1886550&view=diff
==============================================================================
--- pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java (original)
+++ pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java Mon Feb 15 
21:55:20 2021
@@ -23,104 +23,138 @@ import org.apache.pivot.serialization.Se
 import org.apache.pivot.util.Utils;
 
 /**
- * Class representing a span of characters. The range includes all values
- * in the interval <i><code>[start, start+length-1]</code></i> inclusive.  
This is the paradigm
- * used in a lot of places (notably the text controls) to indicate a selection.
+ * Immutable class representing a span of characters. The range includes all 
values
+ * in the interval <i><code>[start, start+length-1]</code></i> inclusive.  
This is
+ * the paradigm used in a lot of places (notably the text controls) to 
indicate a selection.
  * <p> A zero-length span indicates a single caret position at the given start.
  * <p> Negative lengths are not supported and will throw exceptions, as will
  * negative start positions.
  */
 public final class CharSpan {
+    /** The starting location of this span (zero-based). */
     public final int start;
+    /** The length of this span (non-negative). */
     public final int length;
 
+    /** The dictionary key used to retrieve the start location. */
     public static final String START_KEY = "start";
+    /** The dictionary key used to retrieve the length. */
     public static final String LENGTH_KEY = "length";
 
     /**
+     * A span of length zero, starting at position zero.
+     */
+    public static final CharSpan ZERO = new CharSpan();
+
+
+    /**
+     * Construct a default span of length zero at location zero.
+     */
+    public CharSpan() {
+        this(0);
+    }
+
+    /**
      * Construct a new char span of length zero at the given location.
      *
-     * @param start The start of this char span.
+     * @param startValue The start of this char span.
+     * @throws IllegalArgumentException if the value is negative.
      */
-    public CharSpan(final int start) {
-        Utils.checkNonNegative(start, "start");
-        this.start = start;
-        this.length = 0;
+    public CharSpan(final int startValue) {
+        this(startValue, 0);
     }
 
     /**
      * Construct a new char span with the given values.
-     * @param start The start of this char span.
-     * @param length The length of this char span.
-     */
-    public CharSpan(final int start, final int length) {
-        Utils.checkNonNegative(start, "start");
-        Utils.checkNonNegative(length, "length");
-        this.start = start;
-        this.length = length;
+     *
+     * @param startValue The start of this char span.
+     * @param lengthValue The length of this char span.
+     * @throws IllegalArgumentException if either value is negative.
+     */
+    public CharSpan(final int startValue, final int lengthValue) {
+        Utils.checkNonNegative(startValue, "start");
+        Utils.checkNonNegative(lengthValue, "length");
+
+        start = startValue;
+        length = lengthValue;
     }
 
     /**
      * Construct a new char span from another one (a "copy constructor").
      *
-     * @param charSpan An existing char span (which must not be {@code null}).
+     * @param existingCharSpan An existing char span (which must not be {@code 
null}).
      * @throws IllegalArgumentException if the given char span is {@code null}.
      */
-    public CharSpan(final CharSpan charSpan) {
-        Utils.checkNull(charSpan, "charSpan");
+    public CharSpan(final CharSpan existingCharSpan) {
+        Utils.checkNull(existingCharSpan, "existingCharSpan");
 
-        this.start = charSpan.start;
-        this.length = charSpan.length;
+        start = existingCharSpan.start;
+        length = existingCharSpan.length;
     }
 
     /**
-     * Construct a new char span from the given dictionary which must
-     * contain the {@link #START_KEY} and {@link #LENGTH_KEY} keys.
+     * Construct a new char span from the given dictionary which must contain
+     * the {@link #START_KEY} and can also contain the {@link #LENGTH_KEY} key.
      *
-     * @param charSpan A dictionary containing start and end values.
-     * @throws IllegalArgumentException if the given char span is {@code null}
-     * or if the dictionary does not contain the start and length keys.
+     * @param charSpanDictionary A dictionary containing start and length 
values.
+     * @throws IllegalArgumentException if the given char span is {@code null},
+     * if the dictionary does not contain at least the start key, or if either 
of
+     * the dictionary values is negative.
      */
-    public CharSpan(final Dictionary<String, ?> charSpan) {
-        Utils.checkNull(charSpan, "charSpan");
+    public CharSpan(final Dictionary<String, ?> charSpanDictionary) {
+        Utils.checkNull(charSpanDictionary, "charSpanDictionary");
 
-        if (!charSpan.containsKey(START_KEY)) {
+        int startValue;
+        int lengthValue = 0;
+
+        if (charSpanDictionary.containsKey(START_KEY)) {
+            startValue = charSpanDictionary.getInt(START_KEY);
+            Utils.checkNonNegative(startValue, "start");
+        } else {
             throw new IllegalArgumentException(START_KEY + " is required.");
         }
 
-        if (!charSpan.containsKey(LENGTH_KEY)) {
-            throw new IllegalArgumentException(LENGTH_KEY + " is required.");
+        if (charSpanDictionary.containsKey(LENGTH_KEY)) {
+            lengthValue = charSpanDictionary.getInt(LENGTH_KEY);
+            Utils.checkNonNegative(lengthValue, "length");
         }
 
-        int start = charSpan.getInt(START_KEY);
-        int length = charSpan.getInt(LENGTH_KEY);
-
-        Utils.checkNonNegative(start, "start");
-        Utils.checkNonNegative(length, "length");
-
-        this.start = start;
-        this.length = length;
+        start = startValue;
+        length = lengthValue;
     }
 
     /**
      * Construct a new char span from the given sequence with two
      * numeric values corresponding to the start and length values
-     * respectively.
+     * respectively, or one numeric value corresponding to the start
+     * value (length 0).
      *
-     * @param charSpan A sequence containing the start and length values.
-     * @throws IllegalArgumentException if the given char span is {@code null}.
-     */
-    public CharSpan(final Sequence<?> charSpan) {
-        Utils.checkNull(charSpan, "charSpan");
+     * @param charSpanSequence A sequence containing the start and length 
values.
+     * @throws IllegalArgumentException if the given char span is {@code 
null}, or
+     * zero length, or length is greater than two.
+     */
+    public CharSpan(final Sequence<?> charSpanSequence) {
+        Utils.checkNull(charSpanSequence, "charSpanSequence");
+
+        int startValue;
+        int lengthValue = 0;
+        int seqLength = charSpanSequence.getLength();
 
-        int start = ((Number) charSpan.get(0)).intValue();
-        int length = ((Number) charSpan.get(1)).intValue();
+        if (seqLength < 1 || seqLength > 2) {
+            throw new IllegalArgumentException("CharSpan needs one or two 
values in the sequence to construct.");
+        }
 
-        Utils.checkNonNegative(start, "start");
-        Utils.checkNonNegative(length, "length");
+        startValue = ((Number) charSpanSequence.get(0)).intValue();
+        Utils.checkNonNegative(startValue, "start");
 
-        this.start = start;
-        this.length = length;
+        if (seqLength == 2) {
+            lengthValue = ((Number) charSpanSequence.get(1)).intValue();
+            Utils.checkNonNegative(lengthValue, "length");
+        }
+
+
+        start = startValue;
+        length = lengthValue;
     }
 
     /**
@@ -143,7 +177,7 @@ public final class CharSpan {
      * @throws IllegalArgumentException if the updated start value goes 
negative.
      */
     public CharSpan offset(final int offset) {
-        return new CharSpan(this.start + offset, this.length);
+        return (offset == 0) ? this : new CharSpan(this.start + offset, 
this.length);
     }
 
     /**
@@ -156,13 +190,17 @@ public final class CharSpan {
      * @throws IllegalArgumentException if the updated length value goes 
negative.
      */
     public CharSpan lengthen(final int offset) {
-        return new CharSpan(this.start, this.length + offset);
+        return (offset == 0) ? this : new CharSpan(this.start, this.length + 
offset);
     }
 
     @Override
     public boolean equals(final Object o) {
         boolean equal = false;
 
+        if (o == this) {
+            return true;
+        }
+
         if (o instanceof CharSpan) {
             CharSpan span = (CharSpan) o;
             equal = (start == span.start && length == span.length);
@@ -196,10 +234,11 @@ public final class CharSpan {
      *
      * @param value The string value to decode into a new char span.
      * @return The decoded char span.
-     * @throws IllegalArgumentException if the value is {@code null} or
+     * @throws IllegalArgumentException if the value is {@code null} or empty,
      * if the string starts with <code>"{"</code> but it cannot be parsed as
-     * a JSON map, or if it starts with <code>"["</code> but cannot be parsed
-     * as a JSON list.
+     * a JSON map, if it starts with <code>"["</code> but cannot be parsed
+     * as a JSON list, or cannot be recognized as a simple list of one or
+     * two integers.
      */
     public static CharSpan decode(final String value) {
         Utils.checkNullOrEmpty(value, "value");

Added: pivot/trunk/core/test/org/apache/pivot/text/test/CharSpanTest.java
URL: 
http://svn.apache.org/viewvc/pivot/trunk/core/test/org/apache/pivot/text/test/CharSpanTest.java?rev=1886550&view=auto
==============================================================================
--- pivot/trunk/core/test/org/apache/pivot/text/test/CharSpanTest.java (added)
+++ pivot/trunk/core/test/org/apache/pivot/text/test/CharSpanTest.java Mon Feb 
15 21:55:20 2021
@@ -0,0 +1,107 @@
+/*
+ * 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.pivot.text.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+import org.apache.pivot.collections.ArrayAdapter;
+import org.apache.pivot.collections.HashMap;
+import org.apache.pivot.json.JSONSerializer;
+import org.apache.pivot.serialization.SerializationException;
+import org.apache.pivot.text.CharSpan;
+
+
+/**
+ * Tests the {@link CharSpan} class and its various constructors and
+ * utility methods.
+ */
+public class CharSpanTest {
+    @Test
+    public void testConstructors() {
+        assertEquals(CharSpan.ZERO, new CharSpan());
+        assertEquals(CharSpan.ZERO, new CharSpan(0));
+        assertEquals(CharSpan.ZERO, new CharSpan(0, 0));
+
+        assertEquals(new CharSpan(10), new CharSpan(10, 0));
+        try {
+            assertEquals(new CharSpan(3, 4), new 
CharSpan(JSONSerializer.parseList("[ 3, 4]")));
+            assertEquals(new CharSpan(1, 17), new 
CharSpan(JSONSerializer.parseMap("{start:1,length:17}")));
+        } catch (SerializationException se) {
+            fail("Test failure: unexpected exception: " + se.getMessage());
+        }
+
+        try {
+            new CharSpan(-1, 0);
+            fail("Negative start value should throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            System.out.println("Caught the expected exception for negative 
start: " + iae.getMessage());
+        }
+        try {
+            new CharSpan(2, -3);
+            fail("Negative length value should throw 
IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            System.out.println("Caught the expected exception for negative 
length: " + iae.getMessage());
+        }
+        try {
+            new CharSpan(JSONSerializer.parseMap("{span:11}"));
+            fail("Missing start value should throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            System.out.println("Caught the expected exception for missing 
start: " + iae.getMessage());
+        } catch (SerializationException se) {
+            fail("Test failure: unexpected exception: " + se.getMessage());
+        }
+
+        HashMap<String, String> stringMap = new HashMap<>();
+        stringMap.put("start", "12");
+        stringMap.put("length", "3");
+        assertEquals(new CharSpan(12, 3), new CharSpan(stringMap));
+
+        HashMap<String, Long> longMap = new HashMap<>();
+        longMap.put("start", 23L);
+        longMap.put("length", 14L);
+        assertEquals(new CharSpan(23, 14), new CharSpan(longMap));
+
+        ArrayAdapter<Integer> intArray = new ArrayAdapter<Integer>(10, 20);
+        assertEquals(new CharSpan(10, 20), new CharSpan(intArray));
+    }
+
+    @Test
+    public void testDecode() {
+        assertEquals(new CharSpan(1, 2), CharSpan.decode("[1, 2]"));
+        assertEquals(new CharSpan(15), CharSpan.decode("15"));
+        assertEquals(new CharSpan(200), CharSpan.decode("[200]"));
+        assertEquals(new CharSpan(20), CharSpan.decode("{start:20}"));
+        assertEquals(new CharSpan(10, 5), CharSpan.decode("10, 5"));
+        assertEquals(new CharSpan(30, 1), CharSpan.decode("{start:30, 
length:1}"));
+    }
+
+    @Test
+    public void testUtilityMethods() {
+        CharSpan cs1 = new CharSpan(1);
+        assertEquals(cs1.getEnd(), 0);
+        CharSpan cs2 = new CharSpan(100, 50);
+        assertEquals(cs2.getEnd(), 149);
+        assertEquals(cs1.offset(0), cs1);
+        assertEquals(cs1.offset(99), new CharSpan(100));
+        assertEquals(cs2.lengthen(0), cs2);
+        assertEquals(cs2.lengthen(-25), new CharSpan(100, 25));
+    }
+}
+


Reply via email to