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)); + } +} +